Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e45f7ebf4 | ||
|
|
0e05bb61bc | ||
|
|
0635ea7a35 |
4
pom.xml
4
pom.xml
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.ryanmichela</groupId>
|
||||
<artifactId>sshd</artifactId>
|
||||
<version>1.3.4.2</version>
|
||||
<version>1.3.5</version>
|
||||
<url>https://github.com/Justasic/Bukkit-SSHD/</url>
|
||||
|
||||
<properties>
|
||||
@@ -56,6 +56,8 @@
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-common</artifactId>
|
||||
<version>2.3.0</version>
|
||||
<scope>compile</scope>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -11,30 +11,38 @@ import java.util.Map;
|
||||
*/
|
||||
public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
|
||||
|
||||
private Map<String, Integer> failCounts = new HashMap<String, Integer>();
|
||||
private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String username, String password, ServerSession serverSession) {
|
||||
if (SshdPlugin.instance.getConfig().getString("credentials." + username).equals(password)) {
|
||||
failCounts.put(username, 0);
|
||||
return true;
|
||||
}
|
||||
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using password authentication.");
|
||||
@Override
|
||||
public boolean authenticate(String username, String password, ServerSession ss)
|
||||
{
|
||||
if (SshdPlugin.instance.getConfig().getString("Credentials." + username).equals(password))
|
||||
{
|
||||
FailCounts.put(username, 0);
|
||||
return true;
|
||||
}
|
||||
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using password authentication.");
|
||||
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
if (failCounts.containsKey(username)) {
|
||||
failCounts.put(username, failCounts.get(username) + 1);
|
||||
} else {
|
||||
failCounts.put(username, 1);
|
||||
}
|
||||
if (failCounts.get(username) >= 3) {
|
||||
failCounts.put(username, 0);
|
||||
serverSession.close(true);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// do nothing
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries");
|
||||
|
||||
try
|
||||
{
|
||||
Thread.sleep(3000);
|
||||
if (this.FailCounts.containsKey(username))
|
||||
this.FailCounts.put(username, this.FailCounts.get(username) + 1);
|
||||
else
|
||||
this.FailCounts.put(username, 1);
|
||||
|
||||
if (this.FailCounts.get(username) >= tries)
|
||||
{
|
||||
this.FailCounts.put(username, 0);
|
||||
ss.close(true);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,55 @@ import java.util.logging.LogRecord;
|
||||
public class ConsoleLogFormatter extends Formatter {
|
||||
|
||||
private SimpleDateFormat dateFormat;
|
||||
private static final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
|
||||
|
||||
public ConsoleLogFormatter() {
|
||||
this.dateFormat = new SimpleDateFormat("HH:mm:ss");
|
||||
}
|
||||
|
||||
public static String ColorizeString(String str)
|
||||
{
|
||||
// ORIGINAL CODE FROM org.bukkit.craftbukkit.command.ColouredConsoleSender
|
||||
|
||||
replacements.put(ChatColor.BLACK, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).boldOff().toString());
|
||||
replacements.put(ChatColor.DARK_BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).boldOff().toString());
|
||||
replacements.put(ChatColor.DARK_GREEN, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).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.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.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.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.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.MAGIC, Ansi.ansi().a(Ansi.Attribute.BLINK_SLOW).toString());
|
||||
replacements.put(ChatColor.BOLD, Ansi.ansi().a(Ansi.Attribute.UNDERLINE_DOUBLE).toString());
|
||||
replacements.put(ChatColor.STRIKETHROUGH, Ansi.ansi().a(Ansi.Attribute.STRIKETHROUGH_ON).toString());
|
||||
replacements.put(ChatColor.UNDERLINE, Ansi.ansi().a(Ansi.Attribute.UNDERLINE).toString());
|
||||
replacements.put(ChatColor.ITALIC, Ansi.ansi().a(Ansi.Attribute.ITALIC).toString());
|
||||
replacements.put(ChatColor.RESET, Ansi.ansi().a(Ansi.Attribute.RESET).toString());
|
||||
|
||||
String result = str;
|
||||
for (ChatColor color : ChatColor.values())
|
||||
{
|
||||
if (replacements.containsKey(color))
|
||||
{
|
||||
result = result.replaceAll("(?i)" + color.toString(), replacements.get(color));
|
||||
}
|
||||
else
|
||||
{
|
||||
result = result.replaceAll("(?i)" + color.toString(), "");
|
||||
}
|
||||
}
|
||||
result += Ansi.ansi().reset().toString();
|
||||
return result;
|
||||
}
|
||||
|
||||
public String format(LogRecord logrecord) {
|
||||
try {
|
||||
Class.forName("org.bukkit.craftbukkit.command.ColouredConsoleSender");
|
||||
@@ -50,52 +94,10 @@ public class ConsoleLogFormatter extends Formatter {
|
||||
return stringbuilder.toString();
|
||||
}
|
||||
|
||||
private void colorize(LogRecord logrecord) {
|
||||
// ORIGINAL CODE FROM org.bukkit.craftbukkit.command.ColouredConsoleSender
|
||||
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
|
||||
.put(ChatColor.DARK_BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).boldOff().toString());
|
||||
replacements.put(ChatColor.DARK_GREEN,
|
||||
Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).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.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.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.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.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.MAGIC, Ansi.ansi().a(Ansi.Attribute.BLINK_SLOW).toString());
|
||||
replacements.put(ChatColor.BOLD, Ansi.ansi().a(Ansi.Attribute.UNDERLINE_DOUBLE).toString());
|
||||
replacements.put(ChatColor.STRIKETHROUGH, Ansi.ansi().a(Ansi.Attribute.STRIKETHROUGH_ON).toString());
|
||||
replacements.put(ChatColor.UNDERLINE, Ansi.ansi().a(Ansi.Attribute.UNDERLINE).toString());
|
||||
replacements.put(ChatColor.ITALIC, Ansi.ansi().a(Ansi.Attribute.ITALIC).toString());
|
||||
replacements.put(ChatColor.RESET, Ansi.ansi().a(Ansi.Attribute.RESET).toString());
|
||||
|
||||
String result = logrecord.getMessage();
|
||||
for (ChatColor color : ChatColor.values()) {
|
||||
if (replacements.containsKey(color)) {
|
||||
result = result.replaceAll("(?i)" + color.toString(), replacements.get(color));
|
||||
} else {
|
||||
result = result.replaceAll("(?i)" + color.toString(), "");
|
||||
}
|
||||
}
|
||||
result += Ansi.ansi().reset().toString();
|
||||
logrecord.setMessage(result);
|
||||
private void colorize(LogRecord logrecord)
|
||||
{
|
||||
String result = ColorizeString(logrecord.getMessage());
|
||||
logrecord.setMessage(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.ryanmichela.sshd.SshTerminal;
|
||||
import com.ryanmichela.sshd.SshdPlugin;
|
||||
import com.ryanmichela.sshd.StreamHandlerAppender;
|
||||
import com.ryanmichela.sshd.implementations.SSHDCommandSender;
|
||||
import com.ryanmichela.sshd.ConsoleLogFormatter;
|
||||
import jline.console.ConsoleReader;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.Logger;
|
||||
@@ -22,142 +23,160 @@ import org.bukkit.Bukkit;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.StreamHandler;
|
||||
|
||||
public class ConsoleShellFactory implements ShellFactory {
|
||||
|
||||
static SSHDCommandSender sshdCommandSender = new SSHDCommandSender();
|
||||
public Command createShell(ChannelSession cs) {
|
||||
return new ConsoleShell();
|
||||
}
|
||||
|
||||
public Command createShell(ChannelSession cs) {
|
||||
return new ConsoleShell();
|
||||
}
|
||||
public class ConsoleShell implements Command, Runnable {
|
||||
|
||||
public static class ConsoleShell implements Command, Runnable {
|
||||
private InputStream in;
|
||||
private OutputStream out;
|
||||
private OutputStream err;
|
||||
private ExitCallback callback;
|
||||
private Environment environment;
|
||||
private Thread thread;
|
||||
private String Username;
|
||||
|
||||
private InputStream in;
|
||||
private OutputStream out;
|
||||
private OutputStream err;
|
||||
private ExitCallback callback;
|
||||
private Environment environment;
|
||||
private Thread thread;
|
||||
StreamHandlerAppender streamHandlerAppender;
|
||||
public ConsoleReader ConsoleReader;
|
||||
public SSHDCommandSender SshdCommandSender;
|
||||
|
||||
StreamHandlerAppender streamHandlerAppender;
|
||||
public static ConsoleReader consoleReader;
|
||||
public InputStream getIn() {
|
||||
return in;
|
||||
}
|
||||
|
||||
public InputStream getIn() {
|
||||
return in;
|
||||
}
|
||||
public OutputStream getOut() {
|
||||
return out;
|
||||
}
|
||||
|
||||
public OutputStream getOut() {
|
||||
return out;
|
||||
}
|
||||
public OutputStream getErr() {
|
||||
return err;
|
||||
}
|
||||
|
||||
public OutputStream getErr() {
|
||||
return err;
|
||||
}
|
||||
public Environment getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
public Environment getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
public void setInputStream(InputStream in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
public void setInputStream(InputStream in) {
|
||||
this.in = in;
|
||||
}
|
||||
public void setOutputStream(OutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public void setOutputStream(OutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
public void setErrorStream(OutputStream err) {
|
||||
this.err = err;
|
||||
}
|
||||
|
||||
public void setErrorStream(OutputStream err) {
|
||||
this.err = err;
|
||||
}
|
||||
public void setExitCallback(ExitCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void setExitCallback(ExitCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void start(ChannelSession cs, Environment env) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
consoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal());
|
||||
consoleReader.setExpandEvents(true);
|
||||
consoleReader.addCompleter(new ConsoleCommandCompleter());
|
||||
{
|
||||
try
|
||||
{
|
||||
this.ConsoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal());
|
||||
this.ConsoleReader.setExpandEvents(true);
|
||||
this.ConsoleReader.addCompleter(new ConsoleCommandCompleter());
|
||||
|
||||
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), consoleReader);
|
||||
streamHandlerAppender = new StreamHandlerAppender(streamHandler);
|
||||
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader);
|
||||
streamHandlerAppender = new StreamHandlerAppender(streamHandler);
|
||||
|
||||
((Logger)LogManager.getRootLogger()).addAppender(streamHandlerAppender);
|
||||
((Logger)LogManager.getRootLogger()).addAppender(streamHandlerAppender);
|
||||
|
||||
environment = env;
|
||||
thread = new Thread(this, "SSHD ConsoleShell " + env.getEnv().get(Environment.ENV_USER));
|
||||
thread.start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException("Error starting shell", e);
|
||||
}
|
||||
}
|
||||
this.environment = env;
|
||||
this.Username = env.getEnv().get(Environment.ENV_USER);
|
||||
this.SshdCommandSender = new SSHDCommandSender();
|
||||
this.SshdCommandSender.console = this;
|
||||
thread = new Thread(this, "SSHD ConsoleShell " + this.Username);
|
||||
thread.start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException("Error starting shell", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void destroy(ChannelSession cs) { ((Logger)LogManager.getRootLogger()).removeAppender(streamHandlerAppender); }
|
||||
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!SshdPlugin.instance.getConfig().getString("mode").equals("RPC"))
|
||||
printPreamble(consoleReader);
|
||||
while (true)
|
||||
{
|
||||
String command = consoleReader.readLine("\r>", null);
|
||||
if (command == null)
|
||||
continue;
|
||||
if (command.equals("exit") || command.equals("quit"))
|
||||
break;
|
||||
Bukkit.getScheduler().runTask(
|
||||
SshdPlugin.instance, () ->
|
||||
{
|
||||
if (SshdPlugin.instance.getConfig().getString("mode").equals("RPC") && command.startsWith("rpc"))
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
callback.onExit(0);
|
||||
}
|
||||
}
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!SshdPlugin.instance.getConfig().getString("Mode").equals("RPC"))
|
||||
printPreamble(this.ConsoleReader);
|
||||
while (true)
|
||||
{
|
||||
String command = this.ConsoleReader.readLine("\r>", null);
|
||||
// The user sent CTRL+D to close the shell, terminate the session.
|
||||
if (command == null)
|
||||
break;
|
||||
// Skip someone spamming enter
|
||||
if (command.trim().isEmpty())
|
||||
continue;
|
||||
// User wants to exit
|
||||
if (command.equals("exit") || command.equals("quit"))
|
||||
break;
|
||||
// Clear the text from the screen (on supported terminals)
|
||||
if (command.equals("cls"))
|
||||
{
|
||||
this.ConsoleReader.clearScreen();
|
||||
continue;
|
||||
}
|
||||
|
||||
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");
|
||||
consoleReader.println("Connected to: " + Bukkit.getServer().getName() + "\r");
|
||||
consoleReader.println("- " + Bukkit.getServer().getMotd() + "\r");
|
||||
consoleReader.println("\r");
|
||||
consoleReader.println("Type 'exit' to exit the shell." + "\r");
|
||||
consoleReader.println("===============================================" + "\r");
|
||||
}
|
||||
Bukkit.getScheduler().runTask(
|
||||
SshdPlugin.instance, () ->
|
||||
{
|
||||
if (SshdPlugin.instance.getConfig().getString("Mode").equals("RPC") && command.startsWith("rpc"))
|
||||
{
|
||||
// NO ECHO NO PREAMBLE AND SHIT
|
||||
String cmd = command.substring("rpc".length() + 1, command.length());
|
||||
Bukkit.dispatchCommand(this.SshdCommandSender, cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
SshdPlugin.instance.getLogger().info("<" + this.Username + "> " + command);
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SshdPlugin.instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH.");
|
||||
callback.onExit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void printPreamble(ConsoleReader cr) throws IOException
|
||||
{
|
||||
cr.println(" _____ _____ _ _ _____" + "\r");
|
||||
cr.println(" / ____/ ____| | | | __ \\" + "\r");
|
||||
cr.println("| (___| (___ | |__| | | | |" + "\r");
|
||||
cr.println(" \\___ \\\\___ \\| __ | | | |" + "\r");
|
||||
cr.println(" ____) |___) | | | | |__| |" + "\r");
|
||||
cr.println("|_____/_____/|_| |_|_____/" + "\r");
|
||||
// Doesn't really guarantee our actual system hostname but
|
||||
// it's better than not having one at all.
|
||||
cr.println("Connected to: " + InetAddress.getLocalHost().getHostName() + " (" + Bukkit.getServer().getName() + ")\r");
|
||||
cr.println(ConsoleLogFormatter.ColorizeString(Bukkit.getServer().getMotd()) + "\r");
|
||||
cr.println("\r");
|
||||
cr.println("Type 'exit' to exit the shell." + "\r");
|
||||
cr.println("===============================================" + "\r");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import org.apache.sshd.server.session.ServerSession;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.io.FileReader;
|
||||
import java.security.PublicKey;
|
||||
|
||||
@@ -16,57 +18,52 @@ import java.security.PublicKey;
|
||||
*/
|
||||
public class PublicKeyAuthenticator implements PublickeyAuthenticator
|
||||
{
|
||||
private File authorizedKeysDir;
|
||||
private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
|
||||
|
||||
private File authorizedKeysDir;
|
||||
|
||||
public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
|
||||
public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
|
||||
|
||||
@Override public boolean authenticate(String username, PublicKey key, ServerSession session)
|
||||
{
|
||||
byte[] keyBytes = key.getEncoded();
|
||||
File keyFile = new File(authorizedKeysDir, username);
|
||||
File keyFile = new File(authorizedKeysDir, username);
|
||||
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries");
|
||||
|
||||
if (keyFile.exists())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Read all the public key entries
|
||||
List<AuthorizedKeyEntry> pklist = AuthorizedKeyEntry.readAuthorizedKeys(keyFile.toPath());
|
||||
|
||||
// Get an authenticator
|
||||
PublickeyAuthenticator auth = PublickeyAuthenticator.fromAuthorizedEntries(username, session, pklist,
|
||||
PublicKeyEntryResolver.IGNORING);
|
||||
|
||||
boolean accepted = auth.authenticate(username, key, session);
|
||||
|
||||
if (accepted)
|
||||
{
|
||||
SshdPlugin.instance.getLogger().info(
|
||||
username + " successfully authenticated via SSH session using key file " + keyFile.getAbsolutePath());
|
||||
// Validate that the logging in user has the same valid SSH key
|
||||
if (auth.authenticate(username, key, session))
|
||||
{
|
||||
FailCounts.put(username, 0);
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
// If the user fails with several SSH keys, then terminate the connection.
|
||||
if (this.FailCounts.containsKey(username))
|
||||
this.FailCounts.put(username, this.FailCounts.get(username) + 1);
|
||||
else
|
||||
this.FailCounts.put(username, 1);
|
||||
|
||||
if (this.FailCounts.get(username) >= tries)
|
||||
{
|
||||
SshdPlugin.instance.getLogger().severe("Failed to parse PEM file. " + keyFile.getAbsolutePath());
|
||||
}
|
||||
*/
|
||||
this.FailCounts.put(username, 0);
|
||||
session.close(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -7,13 +7,13 @@ import jline.TerminalSupport;
|
||||
*/
|
||||
public class SshTerminal extends TerminalSupport {
|
||||
|
||||
protected SshTerminal() {
|
||||
super(true);
|
||||
}
|
||||
protected SshTerminal() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
setAnsiSupported(true);
|
||||
setEchoEnabled(true);
|
||||
}
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
setAnsiSupported(true);
|
||||
setEchoEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ class SshdPlugin extends JavaPlugin
|
||||
instance = this;
|
||||
|
||||
sshd = SshServer.setUpDefaultServer();
|
||||
sshd.setPort(getConfig().getInt("port", 22));
|
||||
String host = getConfig().getString("listenAddress", "all");
|
||||
sshd.setPort(getConfig().getInt("Port", 1025));
|
||||
String host = getConfig().getString("ListenAddress", "all");
|
||||
sshd.setHost(host.equals("all") ? null : host);
|
||||
|
||||
File hostKey = new File(getDataFolder(), "hostkey");
|
||||
@@ -56,7 +56,7 @@ class SshdPlugin extends JavaPlugin
|
||||
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
|
||||
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
|
||||
|
||||
if (getConfig().getBoolean("enableSFTP"))
|
||||
if (getConfig().getBoolean("EnableSFTP"))
|
||||
{
|
||||
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
|
||||
sshd.setFileSystemFactory(
|
||||
|
||||
@@ -24,108 +24,116 @@ import java.util.logging.Level;
|
||||
|
||||
public class SSHDCommandSender implements ConsoleCommandSender, CommandSender {
|
||||
|
||||
private final PermissibleBase perm = new PermissibleBase(this);
|
||||
private final SSHDConversationTracker conversationTracker = new SSHDConversationTracker();
|
||||
private final PermissibleBase perm = new PermissibleBase(this);
|
||||
private final SSHDConversationTracker conversationTracker = new SSHDConversationTracker();
|
||||
// Set by the upstream allocating function
|
||||
public ConsoleShellFactory.ConsoleShell console;
|
||||
|
||||
public void sendMessage(String message) {
|
||||
this.sendRawMessage(message);
|
||||
}
|
||||
public void sendMessage(String message) {
|
||||
this.sendRawMessage(message);
|
||||
}
|
||||
|
||||
public void sendRawMessage(String message) {
|
||||
if(ConsoleShellFactory.ConsoleShell.consoleReader == null) return;
|
||||
try {
|
||||
ConsoleShellFactory.ConsoleShell.consoleReader.println(ChatColor.stripColor(message));
|
||||
} catch (IOException e) {
|
||||
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e);
|
||||
}
|
||||
}
|
||||
public void sendRawMessage(String message)
|
||||
{
|
||||
// What the fuck does this code even do? Are we sending to one client or all of them?
|
||||
if (this.console.ConsoleReader == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
this.console.ConsoleReader.println(ChatColor.stripColor(message));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMessage(String[] messages) {
|
||||
Arrays.asList(messages).forEach(this::sendMessage);
|
||||
}
|
||||
public void sendMessage(String[] messages) {
|
||||
Arrays.asList(messages).forEach(this::sendMessage);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "Console";
|
||||
}
|
||||
public String getName() {
|
||||
return "SSHD Console";
|
||||
}
|
||||
|
||||
public boolean isOp() {
|
||||
return true;
|
||||
}
|
||||
public boolean isOp() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setOp(boolean value) {
|
||||
throw new UnsupportedOperationException("Cannot change operator status of server console");
|
||||
}
|
||||
public void setOp(boolean value) {
|
||||
throw new UnsupportedOperationException("Cannot change operator status of server console");
|
||||
}
|
||||
|
||||
public boolean beginConversation(Conversation conversation) {
|
||||
return this.conversationTracker.beginConversation(conversation);
|
||||
}
|
||||
public boolean beginConversation(Conversation conversation) {
|
||||
return this.conversationTracker.beginConversation(conversation);
|
||||
}
|
||||
|
||||
public void abandonConversation(Conversation conversation) {
|
||||
this.conversationTracker.abandonConversation(conversation, new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller()));
|
||||
}
|
||||
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 abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
|
||||
this.conversationTracker.abandonConversation(conversation, details);
|
||||
}
|
||||
|
||||
public void acceptConversationInput(String input) {
|
||||
this.conversationTracker.acceptConversationInput(input);
|
||||
}
|
||||
public void acceptConversationInput(String input) {
|
||||
this.conversationTracker.acceptConversationInput(input);
|
||||
}
|
||||
|
||||
public boolean isConversing() {
|
||||
return this.conversationTracker.isConversing();
|
||||
}
|
||||
public boolean isConversing() {
|
||||
return this.conversationTracker.isConversing();
|
||||
}
|
||||
|
||||
public boolean isPermissionSet(String name) {
|
||||
return this.perm.isPermissionSet(name);
|
||||
}
|
||||
public boolean isPermissionSet(String name) {
|
||||
return this.perm.isPermissionSet(name);
|
||||
}
|
||||
|
||||
public boolean isPermissionSet(Permission perm) {
|
||||
return this.perm.isPermissionSet(perm);
|
||||
}
|
||||
public boolean isPermissionSet(Permission perm) {
|
||||
return this.perm.isPermissionSet(perm);
|
||||
}
|
||||
|
||||
public boolean hasPermission(String name) {
|
||||
return this.perm.hasPermission(name);
|
||||
}
|
||||
public boolean hasPermission(String name) {
|
||||
return this.perm.hasPermission(name);
|
||||
}
|
||||
|
||||
public boolean hasPermission(Permission perm) {
|
||||
return this.perm.hasPermission(perm);
|
||||
}
|
||||
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, 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) {
|
||||
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, 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 PermissionAttachment addAttachment(Plugin plugin, int ticks) {
|
||||
return this.perm.addAttachment(plugin, ticks);
|
||||
}
|
||||
|
||||
public void removeAttachment(PermissionAttachment attachment) {
|
||||
this.perm.removeAttachment(attachment);
|
||||
}
|
||||
public void removeAttachment(PermissionAttachment attachment) {
|
||||
this.perm.removeAttachment(attachment);
|
||||
}
|
||||
|
||||
public void recalculatePermissions() {
|
||||
this.perm.recalculatePermissions();
|
||||
}
|
||||
public void recalculatePermissions() {
|
||||
this.perm.recalculatePermissions();
|
||||
}
|
||||
|
||||
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
|
||||
return this.perm.getEffectivePermissions();
|
||||
}
|
||||
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
|
||||
return this.perm.getEffectivePermissions();
|
||||
}
|
||||
|
||||
public boolean isPlayer() {
|
||||
return false;
|
||||
}
|
||||
public boolean isPlayer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return Bukkit.getServer();
|
||||
}
|
||||
public Server getServer() {
|
||||
return Bukkit.getServer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,70 +9,70 @@ import java.util.LinkedList;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class SSHDConversationTracker {
|
||||
private LinkedList<Conversation> conversationQueue = new LinkedList<>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
synchronized void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
|
||||
if (!this.conversationQueue.isEmpty()) {
|
||||
if (this.conversationQueue.getFirst() == conversation) {
|
||||
conversation.abandon(details);
|
||||
}
|
||||
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.contains(conversation)) {
|
||||
this.conversationQueue.remove(conversation);
|
||||
}
|
||||
|
||||
if (!this.conversationQueue.isEmpty()) {
|
||||
this.conversationQueue.getFirst().outputNextPrompt();
|
||||
}
|
||||
}
|
||||
if (!this.conversationQueue.isEmpty()) {
|
||||
this.conversationQueue.getFirst().outputNextPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void abandonAllConversations() {
|
||||
LinkedList<Conversation> oldQueue = this.conversationQueue;
|
||||
this.conversationQueue = new LinkedList<>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
synchronized boolean isConversing() {
|
||||
return !this.conversationQueue.isEmpty();
|
||||
}
|
||||
|
||||
public synchronized boolean isConversingModaly() {
|
||||
return this.isConversing() && this.conversationQueue.getFirst().isModal();
|
||||
}
|
||||
public synchronized boolean isConversingModaly() {
|
||||
return this.isConversing() && this.conversationQueue.getFirst().isModal();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
# The IP addresses(s) the SSH server will listen on. Use a comma separated list for multiple addresses.
|
||||
# Leave as "all" for all addresses.
|
||||
listenAddress: all
|
||||
# The port the SSH server will listen on.
|
||||
port: 22
|
||||
ListenAddress: all
|
||||
# The port the SSH server will listen on. Note that anything above 1024 will require you to run
|
||||
# the whole minecraft server with elevated privileges, this is not recommended and you should
|
||||
# use iptables to route packets from a lower port.
|
||||
Port: 1025
|
||||
|
||||
# Operational mode. Don't touch if you don't know what you're doing. Can be either DEFAULT or RPC
|
||||
mode: DEFAULT
|
||||
Mode: DEFAULT
|
||||
|
||||
# Enable built-in SFTP server or not. You'll be able to connect and upload/download files via SFTP protocol.
|
||||
# Might be useful for testing purposes as well , i. e. docker containers.
|
||||
enableSFTP: true
|
||||
EnableSFTP: true
|
||||
|
||||
# Number of times a person can fail to use an SSH key or enter a password
|
||||
# before it terminates the connection.
|
||||
LoginRetries: 3
|
||||
|
||||
# By default, only public key authentication is enabled. This is the most secure mode.
|
||||
# To authorize a user to log in with public key authentication, install their public
|
||||
# PEM certificate in the authorized_users directory. Name the key file with user's user
|
||||
# name (no file extension).
|
||||
# To authorize a user to login with their public key, install their key using the
|
||||
# OpenSSH authorized_keys file format in the authorized_users directory. Name the key
|
||||
# file with the user's username and no extension. Note: If you want to let a user have
|
||||
# many keys, you can append the keys to their file in authorized_users.
|
||||
|
||||
# For less secure username and password based authentication, complete the sections below.
|
||||
credentials:
|
||||
|
||||
# Type of hashing to use for the passwords below.
|
||||
# Options are: PLAIN (insecure), bcrypt, pbkdf, sha256
|
||||
PasswordType: bcrypt
|
||||
|
||||
# Associate each username with a password hash (or the password if the PasswordType is set to PLAIN)
|
||||
Credentials:
|
||||
# user1: password1
|
||||
# user2: password2
|
||||
|
||||
Reference in New Issue
Block a user