From a7918c68aa859ea903947123b6edc054a89bad38 Mon Sep 17 00:00:00 2001 From: Justin Crawford Date: Sun, 13 Oct 2019 01:00:39 -0700 Subject: [PATCH] Finish the port to Sponge. Things seem to work well. --- pom.xml | 124 +++------- .../java/com/ryanmichela/sshd/ChatColor.java | 222 ++++++++++++++++++ .../sshd/ConfigPasswordAuthenticator.java | 6 +- .../sshd/ConsoleCommandCompleter.java | 22 +- .../sshd/ConsoleCommandFactory.java | 4 +- .../ryanmichela/sshd/ConsoleLogFormatter.java | 52 +--- .../ryanmichela/sshd/ConsoleShellFactory.java | 25 +- .../ryanmichela/sshd/FlushyStreamHandler.java | 49 ---- .../com/ryanmichela/sshd/MkpasswdCommand.java | 23 +- .../sshd/PublicKeyAuthenticator.java | 8 +- .../com/ryanmichela/sshd/ReflectionUtil.java | 96 -------- .../java/com/ryanmichela/sshd/SshdPlugin.java | 15 +- .../sshd/StreamHandlerAppender.java | 77 ++++-- .../implementations/SSHDCommandSender.java | 170 ++++++++------ .../com/ryanmichela/sshd/utils/Config.java | 56 ++--- 15 files changed, 472 insertions(+), 477 deletions(-) create mode 100644 src/main/java/com/ryanmichela/sshd/ChatColor.java delete mode 100644 src/main/java/com/ryanmichela/sshd/FlushyStreamHandler.java delete mode 100644 src/main/java/com/ryanmichela/sshd/ReflectionUtil.java diff --git a/pom.xml b/pom.xml index 6c50ae7..d98c7a5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.ryanmichela - Sponge-SSHD + sshd 1.3.7 jar Sponge-SSHD @@ -56,24 +56,6 @@ 0.3.0 - - org.apache.mina - mina-core - 2.1.3 - - - - org.slf4j - slf4j-api - 1.7.28 - - - - org.slf4j - slf4j-jdk14 - 1.7.28 - - jline jline @@ -83,7 +65,7 @@ org.apache.logging.log4j log4j-core - 2.0 + 2.1 @@ -91,25 +73,6 @@ log4j-api 2.1 - - - commons-codec - commons-codec - 1.10 - provided - - - - commons-lang - commons-lang - 2.6 - - - - org.projectlombok - lombok - 1.18.4 - org.spongepowered @@ -123,7 +86,7 @@ clean package - ${project.name}-${project.version} + ${project.artifactId}-${project.version} @@ -138,60 +101,37 @@ - - - pl.project13.maven - git-commit-id-plugin - 2.2.5 + + maven-assembly-plugin + 3.1.1 + + + package + + single + + + + + ${project.artifactId}-${project.version} + false + + jar-with-dependencies + + + - - ${project.basedir}/.git - - - - + + org.apache.maven.plugins - maven-shade-plugin - 3.2.1 - - - package - - shade - - - true - - - - *:*:*:* - - META-INF/*.RSA - META-INF/*.SF - META-INF/*.DSA - - - - true - ${java.io.tmpdir}/dependency-reduced-pom.xml - - - - - - - org.codehaus.mojo - templating-maven-plugin - 1.0-alpha-3 - - - filter-src - - filter-sources - - - - + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + true + + diff --git a/src/main/java/com/ryanmichela/sshd/ChatColor.java b/src/main/java/com/ryanmichela/sshd/ChatColor.java new file mode 100644 index 0000000..0508284 --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/ChatColor.java @@ -0,0 +1,222 @@ +package com.ryanmichela.sshd; +// The below code was taken from md_5's BungeeCord project - Justin +/* +Copyright (c) 2012, md_5. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +The name of the author may not be used to endorse or promote products derived +from this software without specific prior written permission. + +You may not use the software for commercial software hosting services without +written permission from the author. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Simplistic enumeration of all supported color values for chat. + */ +public enum ChatColor +{ + /** + * Represents black. + */ + BLACK( '0', "black" ), + /** + * Represents dark blue. + */ + DARK_BLUE( '1', "dark_blue" ), + /** + * Represents dark green. + */ + DARK_GREEN( '2', "dark_green" ), + /** + * Represents dark blue (aqua). + */ + DARK_AQUA( '3', "dark_aqua" ), + /** + * Represents dark red. + */ + DARK_RED( '4', "dark_red" ), + /** + * Represents dark purple. + */ + DARK_PURPLE( '5', "dark_purple" ), + /** + * Represents gold. + */ + GOLD( '6', "gold" ), + /** + * Represents gray. + */ + GRAY( '7', "gray" ), + /** + * Represents dark gray. + */ + DARK_GRAY( '8', "dark_gray" ), + /** + * Represents blue. + */ + BLUE( '9', "blue" ), + /** + * Represents green. + */ + GREEN( 'a', "green" ), + /** + * Represents aqua. + */ + AQUA( 'b', "aqua" ), + /** + * Represents red. + */ + RED( 'c', "red" ), + /** + * Represents light purple. + */ + LIGHT_PURPLE( 'd', "light_purple" ), + /** + * Represents yellow. + */ + YELLOW( 'e', "yellow" ), + /** + * Represents white. + */ + WHITE( 'f', "white" ), + /** + * Represents magical characters that change around randomly. + */ + MAGIC( 'k', "obfuscated" ), + /** + * Makes the text bold. + */ + BOLD( 'l', "bold" ), + /** + * Makes a line appear through the text. + */ + STRIKETHROUGH( 'm', "strikethrough" ), + /** + * Makes the text appear underlined. + */ + UNDERLINE( 'n', "underline" ), + /** + * Makes the text italic. + */ + ITALIC( 'o', "italic" ), + /** + * Resets all previous chat colors or formats. + */ + RESET( 'r', "reset" ); + /** + * The special character which prefixes all chat colour codes. Use this if + * you need to dynamically convert colour codes from your custom format. + */ + public static final char COLOR_CHAR = '\u00A7'; + public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRr"; + /** + * Pattern to remove all colour codes. + */ + public static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-OR]" ); + /** + * Colour instances keyed by their active character. + */ + private static final Map BY_CHAR = new HashMap(); + /** + * The code appended to {@link #COLOR_CHAR} to make usable colour. + */ + private final char code; + /** + * This colour's colour char prefixed by the {@link #COLOR_CHAR}. + */ + private final String toString; + private final String name; + + public String getName() { return this.name; } + + static + { + for ( ChatColor colour : values() ) + { + BY_CHAR.put( colour.code, colour ); + } + } + + private ChatColor(char code, String name) + { + this.code = code; + this.name = name; + this.toString = new String( new char[] + { + COLOR_CHAR, code + } ); + } + + @Override + public String toString() + { + return toString; + } + + /** + * Strips the given message of all color codes + * + * @param input String to strip of color + * @return A copy of the input string, without any coloring + */ + public static String stripColor(final String input) + { + if ( input == null ) + { + return null; + } + + return STRIP_COLOR_PATTERN.matcher( input ).replaceAll( "" ); + } + + public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) + { + char[] b = textToTranslate.toCharArray(); + for ( int i = 0; i < b.length - 1; i++ ) + { + if ( b[i] == altColorChar && ALL_CODES.indexOf( b[i + 1] ) > -1 ) + { + b[i] = ChatColor.COLOR_CHAR; + b[i + 1] = Character.toLowerCase( b[i + 1] ); + } + } + return new String( b ); + } + + /** + * Get the colour represented by the specified code. + * + * @param code the code to search for + * @return the mapped colour, or null if non exists + */ + public static ChatColor getByChar(char code) + { + return BY_CHAR.get( code ); + } +} diff --git a/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java b/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java index 6d5aaa5..00b3911 100644 --- a/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java +++ b/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java @@ -23,7 +23,7 @@ public class ConfigPasswordAuthenticator implements PasswordAuthenticator { String ConfigHash = SshdPlugin.GetInstance().config.configNode.getNode("Credentials", username.trim(), "password").getString(); if (ConfigHash == null) - SshdPlugin.GetLogger().warn("Config has no such user: " + username); + SshdPlugin.GetInstance().logger.warn("Config has no such user: " + username); else { try @@ -69,7 +69,7 @@ public class ConfigPasswordAuthenticator implements PasswordAuthenticator { } } - SshdPlugin.GetLogger().info("Failed login for " + username + " using " + HashType + "-based password authentication."); + SshdPlugin.GetInstance().logger.info("Failed login for " + username + " using " + HashType + "-based password authentication."); Integer tries = SshdPlugin.GetInstance().LoginRetries; try @@ -83,7 +83,7 @@ public class ConfigPasswordAuthenticator implements PasswordAuthenticator { if (this.FailCounts.get(username) >= tries) { this.FailCounts.put(username, 0); - SshdPlugin.GetLogger().info("Too many failures for " + username + ", disconnecting."); + SshdPlugin.GetInstance().logger.info("Too many failures for " + username + ", disconnecting."); ss.close(true); } } diff --git a/src/main/java/com/ryanmichela/sshd/ConsoleCommandCompleter.java b/src/main/java/com/ryanmichela/sshd/ConsoleCommandCompleter.java index 717f55c..f487fd0 100644 --- a/src/main/java/com/ryanmichela/sshd/ConsoleCommandCompleter.java +++ b/src/main/java/com/ryanmichela/sshd/ConsoleCommandCompleter.java @@ -6,13 +6,23 @@ package com.ryanmichela.sshd; import jline.console.completer.Completer; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.scheduler.SpongeExecutorService; + import java.util.List; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -public class ConsoleCommandCompleter //implements Completer +public class ConsoleCommandCompleter implements Completer { -/* + private SpongeExecutorService MinecraftExecutor; + + public ConsoleCommandCompleter() + { + super(); + this.MinecraftExecutor = Sponge.getScheduler().createSyncExecutor(SshdPlugin.GetInstance()); + } + public int complete(final String buffer, final int cursor, final List candidates) { Waitable> waitable = new Waitable>() @@ -20,12 +30,11 @@ public class ConsoleCommandCompleter //implements Completer @Override protected List evaluate() { - CommandMap commandMap = ReflectionUtil.getProtectedValue(Bukkit.getServer(), "commandMap"); - return commandMap.tabComplete(Bukkit.getServer().getConsoleSender(), buffer); + return Sponge.getCommandManager().getSuggestions(Sponge.getServer().getConsole(), buffer, null); } }; - Bukkit.getScheduler().runTask(SshdPlugin.instance, waitable); + this.MinecraftExecutor.execute(waitable); try { List offers = waitable.get(); @@ -47,7 +56,7 @@ public class ConsoleCommandCompleter //implements Completer } catch (ExecutionException e) { - SshdPlugin.instance.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); + SshdPlugin.GetInstance().logger.warn("Unhandled exception when tab completing", e); } catch (InterruptedException e) { @@ -55,6 +64,5 @@ public class ConsoleCommandCompleter //implements Completer } return cursor; } - */ } diff --git a/src/main/java/com/ryanmichela/sshd/ConsoleCommandFactory.java b/src/main/java/com/ryanmichela/sshd/ConsoleCommandFactory.java index 7ef9cd9..72a0052 100644 --- a/src/main/java/com/ryanmichela/sshd/ConsoleCommandFactory.java +++ b/src/main/java/com/ryanmichela/sshd/ConsoleCommandFactory.java @@ -55,14 +55,14 @@ public class ConsoleCommandFactory implements CommandFactory { { try { - SshdPlugin.GetLogger() + SshdPlugin.GetInstance().logger .info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command); Sponge.getCommandManager().process(Sponge.getServer().getConsole(), command); } catch (Exception e) { - SshdPlugin.GetLogger().error("Error processing command from SSH -" + e.getMessage()); + SshdPlugin.GetInstance().logger.error("Error processing command from SSH -" + e.getMessage()); } finally { diff --git a/src/main/java/com/ryanmichela/sshd/ConsoleLogFormatter.java b/src/main/java/com/ryanmichela/sshd/ConsoleLogFormatter.java index b403c84..f0b2c2c 100644 --- a/src/main/java/com/ryanmichela/sshd/ConsoleLogFormatter.java +++ b/src/main/java/com/ryanmichela/sshd/ConsoleLogFormatter.java @@ -9,20 +9,12 @@ import org.fusesource.jansi.Ansi; import java.io.PrintWriter; import java.io.StringWriter; -import java.text.SimpleDateFormat; import java.util.EnumMap; import java.util.Map; -import java.util.logging.Formatter; -import java.util.logging.LogRecord; -public class ConsoleLogFormatter extends Formatter { - - private SimpleDateFormat dateFormat; - private static final Map replacements = new EnumMap(ChatColor.class); - - public ConsoleLogFormatter() { - this.dateFormat = new SimpleDateFormat("HH:mm:ss"); - } +public class ConsoleLogFormatter +{ + private static final Map replacements = new EnumMap(ChatColor.class); public static String ColorizeString(String str) { @@ -66,43 +58,5 @@ public class ConsoleLogFormatter extends Formatter { result += Ansi.ansi().reset().toString(); return result; } - - public String format(LogRecord logrecord) - { - try - { - Class.forName("org.bukkit.craftbukkit.command.ColouredConsoleSender"); - } - catch (ClassNotFoundException ignored) - { - // MEANS WE'RE ON PAPER/TACO/OTHER SHIT - colorize(logrecord); - } - StringBuilder stringbuilder = new StringBuilder(); - - stringbuilder.append(" ["); - stringbuilder.append(this.dateFormat.format(logrecord.getMillis())).append(" "); - - stringbuilder.append(logrecord.getLevel().getName()).append("]: "); - stringbuilder.append(this.formatMessage(logrecord)); - stringbuilder.append('\n'); - Throwable throwable = logrecord.getThrown(); - - if (throwable != null) - { - StringWriter stringwriter = new StringWriter(); - - throwable.printStackTrace(new PrintWriter(stringwriter)); - stringbuilder.append(stringwriter.toString()); - } - - return stringbuilder.toString().replace("\n", "\r\n"); - } - - private void colorize(LogRecord logrecord) - { - String result = ColorizeString(logrecord.getMessage()); - logrecord.setMessage(result); - } } diff --git a/src/main/java/com/ryanmichela/sshd/ConsoleShellFactory.java b/src/main/java/com/ryanmichela/sshd/ConsoleShellFactory.java index 0d876b8..a8b57a5 100644 --- a/src/main/java/com/ryanmichela/sshd/ConsoleShellFactory.java +++ b/src/main/java/com/ryanmichela/sshd/ConsoleShellFactory.java @@ -3,7 +3,6 @@ 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; @@ -34,7 +33,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.util.StringTokenizer; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.StreamHandler; @@ -97,10 +95,9 @@ public class ConsoleShellFactory implements ShellFactory { { this.ConsoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal()); this.ConsoleReader.setExpandEvents(true); - //this.ConsoleReader.addCompleter(new ConsoleCommandCompleter()); + this.ConsoleReader.addCompleter(new ConsoleCommandCompleter()); - StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader); - this.streamHandlerAppender = new StreamHandlerAppender(streamHandler); + this.streamHandlerAppender = new StreamHandlerAppender(this.ConsoleReader); ((Logger)LogManager.getRootLogger()).addAppender(this.streamHandlerAppender); @@ -132,7 +129,7 @@ public class ConsoleShellFactory implements ShellFactory { printPreamble(this.ConsoleReader); while (true) { - String command = this.ConsoleReader.readLine("\r>", null); + String command = this.ConsoleReader.readLine("\r> ", null); // The user sent CTRL+D to close the shell, terminate the session. if (command == null) break; @@ -153,7 +150,7 @@ public class ConsoleShellFactory implements ShellFactory { // Hide the mkpasswd command input from other users. Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd"); - MinecraftExecutor.schedule(() -> + MinecraftExecutor.submit(() -> { if (SshdPlugin.GetInstance().Mode.equals("RPC") && command.startsWith("rpc")) { @@ -168,7 +165,7 @@ public class ConsoleShellFactory implements ShellFactory { // our plugin and the connected client. if (!mkpasswd) { - SshdPlugin.GetLogger().info("<" + this.Username + "> " + command); + SshdPlugin.GetInstance().logger.info("<" + this.Username + "> " + command); CmdManager.process(Sponge.getServer().getConsole(), command); } else @@ -176,16 +173,16 @@ public class ConsoleShellFactory implements ShellFactory { CmdManager.process(this.SshdCommandSender, command); } } - }, 0, TimeUnit.SECONDS); - } + }); + } } catch (IOException e) { - SshdPlugin.GetLogger().error("Error processing command from SSH", e); + SshdPlugin.GetInstance().logger.error("Error processing command from SSH", e); } finally { - SshdPlugin.GetLogger().info(this.Username + " disconnected from SSH."); + SshdPlugin.GetInstance().logger.info(this.Username + " disconnected from SSH."); callback.onExit(0); } } @@ -203,7 +200,7 @@ public class ConsoleShellFactory implements ShellFactory { } catch (FileNotFoundException e) { - SshdPlugin.GetLogger().warn("Could not open " + f + ": File does not exist."); + SshdPlugin.GetInstance().logger.warn("Could not open " + f + ": File does not exist."); // Not showing the SSH motd is not a fatal failure, let the session continue. } @@ -222,7 +219,7 @@ public class ConsoleShellFactory implements ShellFactory { cr.println(str + "\r"); cr.println(ConsoleLogFormatter.ColorizeString(Sponge.getServer().getMotd().toPlain()).replaceAll("\n", "\r\n")); cr.println("\r"); - cr.println("Type 'exit' to exit the shell." + "\r"); + cr.println("Type 'exit' or CTRL+D to exit the shell." + "\r"); cr.println("===============================================" + "\r"); } } diff --git a/src/main/java/com/ryanmichela/sshd/FlushyStreamHandler.java b/src/main/java/com/ryanmichela/sshd/FlushyStreamHandler.java deleted file mode 100644 index 10c042c..0000000 --- a/src/main/java/com/ryanmichela/sshd/FlushyStreamHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.ryanmichela.sshd; - -import jline.console.ConsoleReader; -import org.apache.sshd.common.SshException; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.logging.*; - -/** - * Copyright 2013 Ryan Michela - */ -public class FlushyStreamHandler extends StreamHandler { - - private ConsoleReader reader; - - public FlushyStreamHandler(OutputStream out, Formatter formatter, ConsoleReader reader) { - super(out, formatter); - this.reader = reader; - setLevel(Level.INFO); - } - - @Override - public synchronized void publish(LogRecord record) { - record.setMessage(record.getMessage().replace("\n", "\n\r")); - super.publish(record); - flush(); - } - - @Override - public synchronized void flush() { - try { - reader.print(ConsoleReader.RESET_LINE + ""); - reader.flush(); - super.flush(); - try { - reader.drawLine(); - } catch (Throwable ex) { - reader.getCursorBuffer().clear(); - } - reader.flush(); - super.flush(); - } catch (SshException ex) { - // do nothing - } catch (IOException ex) { - Logger.getLogger(FlushyStreamHandler.class.getName()).log(Level.SEVERE, null, ex); - } - } -} diff --git a/src/main/java/com/ryanmichela/sshd/MkpasswdCommand.java b/src/main/java/com/ryanmichela/sshd/MkpasswdCommand.java index 3cf5a54..ca51cd6 100644 --- a/src/main/java/com/ryanmichela/sshd/MkpasswdCommand.java +++ b/src/main/java/com/ryanmichela/sshd/MkpasswdCommand.java @@ -35,7 +35,7 @@ public class MkpasswdCommand implements CommandExecutor .executor((CommandSource source, CommandContext args) -> { try { - source.sendMessage(Text.of("\u00A79Your Hash: " + Cryptography.PBKDF2_HashPassword(args.getOne("message").get()))); + source.sendMessage(Text.of("\u00A79Your Hash: " + Cryptography.PBKDF2_HashPassword(args.getOne("password").get()))); } catch (Exception e) { @@ -53,7 +53,7 @@ public class MkpasswdCommand implements CommandExecutor .executor((CommandSource source, CommandContext args) -> { try { - source.sendMessage(Text.of("\u00A79Your Hash: " + Cryptography.BCrypt_HashPassword(args.getOne("message").get()))); + source.sendMessage(Text.of("\u00A79Your Hash: " + Cryptography.BCrypt_HashPassword(args.getOne("password").get()))); } catch (Exception e) { @@ -71,7 +71,7 @@ public class MkpasswdCommand implements CommandExecutor .executor((CommandSource source, CommandContext args) -> { try { - source.sendMessage(Text.of("\u00A79Your Hash: " + Cryptography.SHA256_HashPassword(args.getOne("message").get()))); + source.sendMessage(Text.of("\u00A79Your Hash: " + Cryptography.SHA256_HashPassword(args.getOne("password").get()))); } catch (Exception e) { @@ -82,33 +82,22 @@ public class MkpasswdCommand implements CommandExecutor }) .build(); - // The plain text encryption method + // The plain text "encryption" method CommandSpec plain = CommandSpec.builder() .description(Text.of("Plain text password (insecure)")) .permission("sshd.mkpasswd.plain") + .arguments(GenericArguments.remainingJoinedStrings(Text.of("password"))) .executor((CommandSource source, CommandContext args) -> { source.sendMessage(Text.of("Bro... It's literally your unhashed password.")); return CommandResult.success(); }) .build(); - // The "help" command to show syntax usage - CommandSpec helpcmd = CommandSpec.builder() - .description(Text.of("Display's mkpasswd help")) - .permission("sshd.mkpasswd.help") - .executor((CommandSource source, CommandContext args) -> { - source.sendMessage(Text.of("\u00A7a/mkpasswd \u00A7r")); - source.sendMessage(Text.of("\u00A79Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN\u00A7r")); - return CommandResult.success(); - }) - .build(); - // the root "mkpasswd" command cmdspec = CommandSpec.builder() .description(Text.of("Create an SSHd password using hashes")) .extendedDescription(Text.of("Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN")) .permission("sshd.mkpasswd") - .child(helpcmd, "help", "h") .child(plain, "plain") .child(sha256, "sha256") .child(bcrypt, "bcrypt") @@ -125,7 +114,7 @@ public class MkpasswdCommand implements CommandExecutor public CommandResult execute(CommandSource src, CommandContext args) throws CommandException { // This command doesn't do anything. - src.sendMessage(Text.of("\u00A7a/mkpasswd \u00A7r")); + src.sendMessage(Text.of("\u00A7a/mkpasswd \u00A7r")); src.sendMessage(Text.of("\u00A79Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN\u00A7r")); return CommandResult.success(); } diff --git a/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java b/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java index 6de97e9..43fd275 100644 --- a/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java +++ b/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java @@ -46,7 +46,7 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator } else { - SshdPlugin.GetLogger().info( + SshdPlugin.GetInstance().logger.info( username + " failed authentication via SSH session using key file " + keyFile.getAbsolutePath()); } @@ -59,7 +59,7 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator if (this.FailCounts.get(username) >= tries) { this.FailCounts.put(username, 0); - SshdPlugin.GetLogger().info("Too many failures for " + username + ", disconnecting."); + SshdPlugin.GetInstance().logger.info("Too many failures for " + username + ", disconnecting."); session.close(true); } @@ -67,12 +67,12 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator } catch (Exception e) { - SshdPlugin.GetLogger().error("Failed to process public key " + keyFile.getAbsolutePath() + " " + e.getMessage()); + SshdPlugin.GetInstance().logger.error("Failed to process public key " + keyFile.getAbsolutePath() + " " + e.getMessage()); } } else { - SshdPlugin.GetLogger().error("Could not locate public key for " + username + SshdPlugin.GetInstance().logger.error("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."); } diff --git a/src/main/java/com/ryanmichela/sshd/ReflectionUtil.java b/src/main/java/com/ryanmichela/sshd/ReflectionUtil.java deleted file mode 100644 index 3d8d2d1..0000000 --- a/src/main/java/com/ryanmichela/sshd/ReflectionUtil.java +++ /dev/null @@ -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 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 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; - } - } -} diff --git a/src/main/java/com/ryanmichela/sshd/SshdPlugin.java b/src/main/java/com/ryanmichela/sshd/SshdPlugin.java index 0372d68..342f7ac 100644 --- a/src/main/java/com/ryanmichela/sshd/SshdPlugin.java +++ b/src/main/java/com/ryanmichela/sshd/SshdPlugin.java @@ -51,7 +51,7 @@ public class SshdPlugin private static SshdPlugin instance; @Inject - public static Logger logger; + public Logger logger; @Inject @DefaultConfig(sharedRoot = false) @@ -71,6 +71,11 @@ public class SshdPlugin config = new Config(); config.setup(); + // Make sure our authorized_keys folder exists + File authorizedKeys = new File(this.ConfigDir.toFile(), "authorized_keys"); + if (!authorizedKeys.exists()) + authorizedKeys.mkdirs(); + // Now include it in our dealio here this.Mode = config.configNode.getNode("Mode").getString(); this.PasswordType = config.configNode.getNode("PasswordType").getString(); @@ -99,7 +104,6 @@ public class SshdPlugin sshd.setHost(this.ListenAddress.equals("all") ? null : this.ListenAddress); File hostKey = new File(this.ConfigDir.toFile(), "hostkey"); - File authorizedKeys = new File(this.ConfigDir.toFile(), "authorized_keys"); sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey.toPath())); sshd.setShellFactory(new ConsoleShellFactory()); @@ -109,8 +113,7 @@ public class SshdPlugin if (this.EnableSFTP) { sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory())); - sshd.setFileSystemFactory( - new VirtualFileSystemFactory(this.ConfigDir.getParent().getParent())); + sshd.setFileSystemFactory(new VirtualFileSystemFactory(this.ConfigDir.getParent().getParent())); } MkpasswdCommand.BuildCommand(); @@ -133,8 +136,8 @@ public class SshdPlugin return instance; } - public static Logger GetLogger() + public Logger GetLogger() { - return logger; + return this.logger; } } diff --git a/src/main/java/com/ryanmichela/sshd/StreamHandlerAppender.java b/src/main/java/com/ryanmichela/sshd/StreamHandlerAppender.java index 1af7efe..b8e42e9 100644 --- a/src/main/java/com/ryanmichela/sshd/StreamHandlerAppender.java +++ b/src/main/java/com/ryanmichela/sshd/StreamHandlerAppender.java @@ -1,48 +1,75 @@ package com.ryanmichela.sshd; +import org.apache.logging.log4j.Level; 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 org.apache.logging.log4j.core.layout.PatternLayout; +import jline.console.ConsoleReader; +import org.apache.sshd.common.SshException; + +import java.io.IOException; import java.io.Serializable; import java.util.UUID; -import java.util.logging.LogRecord; -import java.util.logging.StreamHandler; +import java.nio.charset.Charset; /** * Copyright 2014 Ryan Michela */ -public class StreamHandlerAppender implements Appender { - - private StreamHandler streamHandler; +public class StreamHandlerAppender implements Appender +{ + private ConsoleReader console; private UUID uuid; + private PatternLayout MinecraftLayout = PatternLayout.newBuilder().withPattern("%highlightError{[%d{HH:mm:ss} %level] [%logger]: %minecraftFormatting{%msg}%xEx}").build(); + private PatternLayout MojangLayout = PatternLayout.newBuilder().withPattern("%highlightError{[%d{HH:mm:ss} %level]: %minecraftFormatting{%msg}%xEx}").build(); - public StreamHandlerAppender(StreamHandler streamHandler) { - this.streamHandler = streamHandler; + public StreamHandlerAppender(ConsoleReader console) + { + this.console = console; 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; + public void append(LogEvent logEvent) + { + if(true) return; + + if (logEvent.getLevel() == Level.DEBUG || logEvent.getLevel() == Level.TRACE) + return; + + try + { + // Delete the jline's `> ` character + this.console.print(ConsoleReader.BACKSPACE + "" + ConsoleReader.BACKSPACE); + // Print our message + if (logEvent.getLoggerName().matches("net\\.minecraft\\..*|com\\.mojang\\..*")) + this.console.println(ConsoleLogFormatter.ColorizeString(this.MojangLayout.toSerializable(logEvent)).replaceAll("\n", "\r\n") + "\r"); + else + this.console.println(ConsoleLogFormatter.ColorizeString(this.MinecraftLayout.toSerializable(logEvent)).replaceAll("\n", "\r\n") + "\r"); + // Reset the console (colors, formatting, etc) + this.console.print(ConsoleReader.RESET_LINE + ""); + try + { + // Attempt to draw new console line + this.console.drawLine(); + } + catch (Throwable ex) + { + this.console.getCursorBuffer().clear(); + } + // Push it to the end user. + this.console.flush(); + } + catch (SshException ex) + { + // do nothing + } + catch (IOException ex) + { + ex.printStackTrace(); } - String message = logEvent.getMessage().getFormattedMessage(); - - - LogRecord logRecord = new LogRecord(level, message); - streamHandler.publish(logRecord); } @Override diff --git a/src/main/java/com/ryanmichela/sshd/implementations/SSHDCommandSender.java b/src/main/java/com/ryanmichela/sshd/implementations/SSHDCommandSender.java index 204303d..b16a77b 100644 --- a/src/main/java/com/ryanmichela/sshd/implementations/SSHDCommandSender.java +++ b/src/main/java/com/ryanmichela/sshd/implementations/SSHDCommandSender.java @@ -4,6 +4,7 @@ import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.ConsoleShellFactory; import com.ryanmichela.sshd.ConsoleLogFormatter; import org.spongepowered.api.command.source.ConsoleSource; +import org.checkerframework.checker.nullness.Opt; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.text.Text; import org.spongepowered.api.text.channel.MessageChannel; @@ -13,10 +14,13 @@ import org.spongepowered.api.service.permission.SubjectData; import org.spongepowered.api.service.permission.SubjectReference; import org.spongepowered.api.service.context.Context; import org.spongepowered.api.util.Tristate; +import jline.console.ConsoleReader; import java.io.IOException; import java.util.Arrays; +import java.util.HashSet; import java.util.Set; +import java.util.UUID; import java.util.logging.Level; import java.util.Optional; import java.util.List; @@ -28,17 +32,19 @@ public class SSHDCommandSender implements ConsoleSource private Subject subjectDelegate; // Set by the upstream allocating function public ConsoleShellFactory.ConsoleShell console; + private UUID uuid = UUID.randomUUID(); // This is an override for Sponge to work with the SSH consoles. + @Override public void sendMessage(Text message) { - this.sendRawMessage(message.toPlain() + "\r"); + this.sendRawMessage(message.toPlain()); } // Back port from Spigot/BungeeCord-style API calls. public void sendMessage(String message) { - this.sendRawMessage(message + "\r"); + this.sendRawMessage(message); } public void sendRawMessage(String message) @@ -48,8 +54,9 @@ public class SSHDCommandSender implements ConsoleSource return; try { - this.console.ConsoleReader.println(ConsoleLogFormatter.ColorizeString(message).replace("\n", "\n\r")); - this.console.ConsoleReader.print(this.console.ConsoleReader.RESET_LINE + ""); + this.console.ConsoleReader.print(ConsoleReader.BACKSPACE + "" + ConsoleReader.BACKSPACE); + this.console.ConsoleReader.println(ConsoleLogFormatter.ColorizeString(message).replaceAll("\n", "\n\r") + "\r"); + this.console.ConsoleReader.print(ConsoleReader.RESET_LINE + ""); this.console.ConsoleReader.flush(); try { @@ -63,7 +70,7 @@ public class SSHDCommandSender implements ConsoleSource } catch (IOException e) { - SshdPlugin.GetLogger().error("Error sending message to SSHDCommandSender", e); + SshdPlugin.GetInstance().logger.error("Error sending message to SSHDCommandSender", e); } } @@ -86,75 +93,86 @@ public class SSHDCommandSender implements ConsoleSource return "SSHD Console"; } -@Override - public String getIdentifier() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Set getActiveContexts() { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean isSubjectDataPersisted() { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean isChildOf(Set contexts, SubjectReference parent) { - // TODO Auto-generated method stub - return false; - } - - @Override - public SubjectData getTransientSubjectData() { - // TODO Auto-generated method stub - return null; - } - - @Override - public SubjectData getSubjectData() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Tristate getPermissionValue(Set contexts, String permission) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List getParents(Set contexts) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Optional getOption(Set contexts, String key) { - // TODO Auto-generated method stub - return null; - } - - @Override - public SubjectCollection getContainingCollection() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Optional getCommandSource() { - // TODO Auto-generated method stub - return null; - } - - @Override - public SubjectReference asSubjectReference() { - // TODO Auto-generated method stub - return null; - } + @Override + public String getIdentifier() { + return uuid.toString(); + } + + @Override + public Set getActiveContexts() + { + // No clue what any of this does but sponge needs it to work with this class. - Justin + Set set = new HashSet(); + set.add(new Context(Context.USER_KEY, "SSHD")); + return set; + } + + @Override + public boolean isSubjectDataPersisted() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isChildOf(Set contexts, SubjectReference parent) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public SubjectData getTransientSubjectData() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public SubjectData getSubjectData() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Tristate getPermissionValue(Set contexts, String permission) + { + // We're allowed to view all permissions. + return Tristate.TRUE; + } + + @Override + public List getParents(Set contexts) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Optional getOption(Set contexts, String key) + { + return Optional.empty(); + } + + @Override + public SubjectCollection getContainingCollection() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Optional getCommandSource() + { + // TODO Auto-generated method stub + return Optional.of(this); + } + + @Override + public SubjectReference asSubjectReference() + { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/com/ryanmichela/sshd/utils/Config.java b/src/main/java/com/ryanmichela/sshd/utils/Config.java index 52c0397..6cc58ea 100644 --- a/src/main/java/com/ryanmichela/sshd/utils/Config.java +++ b/src/main/java/com/ryanmichela/sshd/utils/Config.java @@ -74,54 +74,36 @@ public class Config ("The IP addresses(s) the SSH server will listen on. Use a comma separated list for multiple addresses.\n" + "Leave as \"all\" for all addresses."); this.configNode.getNode("Port").setValue("1025").setComment( - "# The port the SSH server will listen on. Note that anything above 1024 will require you to run\n" + - "# the whole minecraft server with elevated privileges, this is not recommended and you should\n" + - "# use iptables to route packets from a lower port."); + "The port the SSH server will listen on. Note that anything above 1024 will require you to run\n" + + "the whole minecraft server with elevated privileges, this is not recommended and you should\n" + + "use iptables to route packets from a lower port."); this.configNode.getNode("Mode").setValue("DEFAULT").setComment("Operational mode. Don't touch if you don't know what you're doing. Can be either DEFAULT or RPC"); this.configNode.getNode("EnableSFTP").setValue("true").setComment( - "# Enable built-in SFTP server or not. You'll be able to connect and upload/download files via SFTP protocol.\n" + - "# Might be useful for testing purposes as well , i. e. docker containers."); + "Enable built-in SFTP server or not. You'll be able to connect and upload/download files via SFTP protocol.\n" + + "Might be useful for testing purposes as well , i. e. docker containers."); this.configNode.getNode("LoginRetries").setValue("3").setComment( - "# Number of times a person can fail to use an SSH key or enter a password\n" + - "# before it terminates the connection."); + "Number of times a person can fail to use an SSH key or enter a password\n" + + "before it terminates the connection."); this.configNode.getNode("PasswordType").setValue("bcrypt").setComment ("########################################################################################\n" + - "# By default, only public key authentication is enabled. This is the most secure mode.\n" + - "# To authorize a user to login with their public key, install their key using the\n" + - "# OpenSSH authorized_keys file format in the authorized_users directory. Name the key\n" + - "# file with the user's username and no extension. Note: If you want to let a user have\n" + - "# many keys, you can append the keys to their file in authorized_users.\n" + + "By default, only public key authentication is enabled. This is the most secure mode.\n" + + "To authorize a user to login with their public key, install their key using the\n" + + "OpenSSH authorized_keys file format in the authorized_users directory. Name the key\n" + + "file with the user's username and no extension. Note: If you want to let a user have\n" + + "many keys, you can append the keys to their file in authorized_users.\n" + "########################################################################################\n" + "For less secure username and password based authentication, complete the sections below.\n" + "\n" + - "# Type of hashing to use for the passwords below.\n" + - "# Options are: PLAIN (insecure), bcrypt, pbkdf2, sha256\n" + - "#\n" + - "# You can use the console/in-game command `/mkpasswd [hash] PASSWORD` to\n" + - "# generate a password hash string then copy it for your passwords below.\n" + - "# You can also use `/mkpasswd help` to see what algorithms are supported."); + "Type of hashing to use for the passwords below.\n" + + "Options are: PLAIN (insecure), bcrypt, pbkdf2, sha256\n" + + "\n" + + "You can use the console/in-game command `/mkpasswd [hash] PASSWORD` to\n" + + "generate a password hash string then copy it for your passwords below.\n" + + "You can also use `/mkpasswd help` to see what algorithms are supported."); - this.configNode.getNode("Credentials").setComment("# Associate each username with a password hash (or the password if the PasswordType is set to PLAIN)"); + this.configNode.getNode("Credentials").setComment("Associate each username with a password hash (or the password if the PasswordType is set to PLAIN)"); this.configNode.getNode("Credentials", "user1", "password").setValue("MySecretPassword"); this.configNode.getNode("Credentials", "user2", "password").setValue("MyBestFriendsPassword"); - //this.configNode.getNode("").setValue("").setComment(""); - - - /* - this.Mode = config.configNode.getNode("Mode").getString(); - this.PasswordType = config.configNode.getNode("PasswordType").getString(); - this.ListenAddress = config.configNode.getNode("ListenAddress").getString(); - this.Port = config.configNode.getNode("Port").getInt(); - this.LoginRetries = config.configNode.getNode("LoginRetries").getInt(); - this.EnableSFTP = config.configNode.getNode("EnableSFTP").getBoolean(); - - this.configNode.getNode("mysql").setComment("MySQL database for Whitelisting"); - this.configNode.getNode("mysql", "port").setValue(3306).setComment("MySQL server port"); - this.configNode.getNode("mysql", "host").setValue("localhost").setComment("MySQL server to connect to"); - this.configNode.getNode("mysql", "database").setValue("WhitelistSync").setComment("MySQL database for Whitelisting"); - this.configNode.getNode("mysql", "username").setValue("Whitelist").setComment("MySQL username for the database"); - this.configNode.getNode("mysql", "password").setValue("letmein").setComment("MySQL password for the database"); - */ } }