From e98113397db13259590aa3941acf441f67de8781 Mon Sep 17 00:00:00 2001 From: Ryan Michela Date: Wed, 13 Nov 2013 23:17:51 -0800 Subject: [PATCH] Initial commit. --- .gitignore | 3 + pom.xml | 95 +++++ .../sshd/ConfigPasswordAuthenticator.java | 39 ++ .../sshd/ConsoleCommandCompleter.java | 46 +++ .../sshd/ConsoleCommandFactory.java | 67 ++++ .../ryanmichela/sshd/ConsoleShellFactory.java | 128 +++++++ .../ryanmichela/sshd/FlushyOutputStream.java | 33 ++ .../ryanmichela/sshd/FlushyStreamHandler.java | 48 +++ .../java/com/ryanmichela/sshd/PemDecoder.java | 97 +++++ .../sshd/PublicKeyAuthenticator.java | 50 +++ .../com/ryanmichela/sshd/ReflectionUtil.java | 98 +++++ .../com/ryanmichela/sshd/SshTerminal.java | 19 + .../java/com/ryanmichela/sshd/SshdPlugin.java | 62 +++ .../java/com/ryanmichela/sshd/Waitable.java | 48 +++ .../org/slf4j/impl/PluginSlf4jFactory.java | 353 ++++++++++++++++++ .../org/slf4j/impl/StaticLoggerBinder.java | 82 ++++ src/main/resources/config.yml | 12 + src/main/resources/plugin.yml | 4 + 18 files changed, 1284 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java create mode 100644 src/main/java/com/ryanmichela/sshd/ConsoleCommandCompleter.java create mode 100644 src/main/java/com/ryanmichela/sshd/ConsoleCommandFactory.java create mode 100644 src/main/java/com/ryanmichela/sshd/ConsoleShellFactory.java create mode 100644 src/main/java/com/ryanmichela/sshd/FlushyOutputStream.java create mode 100644 src/main/java/com/ryanmichela/sshd/FlushyStreamHandler.java create mode 100644 src/main/java/com/ryanmichela/sshd/PemDecoder.java create mode 100644 src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java create mode 100644 src/main/java/com/ryanmichela/sshd/ReflectionUtil.java create mode 100644 src/main/java/com/ryanmichela/sshd/SshTerminal.java create mode 100644 src/main/java/com/ryanmichela/sshd/SshdPlugin.java create mode 100644 src/main/java/com/ryanmichela/sshd/Waitable.java create mode 100644 src/main/java/org/slf4j/impl/PluginSlf4jFactory.java create mode 100644 src/main/java/org/slf4j/impl/StaticLoggerBinder.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore index 0f182a0..e981ba3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ *.jar *.war *.ear +.idea +*.iml +target diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a278ab3 --- /dev/null +++ b/pom.xml @@ -0,0 +1,95 @@ + + + 4.0.0 + + com.ryanmichela + SSHD + 1.0 + http://dev.bukkit.org/server-mods/sshd/ + + + + + bukkit-repo + http://repo.bukkit.org/content/groups/public/ + + + + + + + GPL2 + http://www.gnu.org/licenses/gpl-2.0.html + + + + + + + org.bukkit + craftbukkit + 1.6.4-R1.0 + provided + jar + + + org.apache.sshd + sshd-core + 0.9.0 + compile + jar + + + + + + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 1.5 + + + package + + shade + + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.0 + + 1.7 + 1.7 + true + + + + + + jar + \ No newline at end of file diff --git a/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java b/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java new file mode 100644 index 0000000..256453c --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java @@ -0,0 +1,39 @@ +package com.ryanmichela.sshd; + +import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.session.ServerSession; + +import java.util.HashMap; +import java.util.Map; + +/** + * Copyright 2013 Ryan Michela + */ +public class ConfigPasswordAuthenticator implements PasswordAuthenticator { + private Map failCounts = new HashMap(); + + @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."); + + 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; + } +} diff --git a/src/main/java/com/ryanmichela/sshd/ConsoleCommandCompleter.java b/src/main/java/com/ryanmichela/sshd/ConsoleCommandCompleter.java new file mode 100644 index 0000000..ef27f5c --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/ConsoleCommandCompleter.java @@ -0,0 +1,46 @@ +package com.ryanmichela.sshd; + +/** + * Copyright 2013 Ryan Michela + */ + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandMap; +import org.bukkit.craftbukkit.libs.jline.console.completer.Completer; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; + +public class ConsoleCommandCompleter implements Completer { + public int complete(final String buffer, final int cursor, final List candidates) { + Waitable> waitable = new Waitable>() { + @Override + protected List evaluate() { + CommandMap commandMap = ReflectionUtil.getProtectedValue(Bukkit.getServer(), "commandMap"); + return commandMap.tabComplete(Bukkit.getServer().getConsoleSender(), buffer); + } + }; + Bukkit.getScheduler().runTask(SshdPlugin.instance, waitable); + try { + List offers = waitable.get(); + if (offers == null) { + return cursor; + } + candidates.addAll(offers); + + final int lastSpace = buffer.lastIndexOf(' '); + if (lastSpace == -1) { + return cursor - buffer.length(); + } else { + return cursor - (buffer.length() - lastSpace - 1); + } + } catch (ExecutionException e) { + SshdPlugin.instance.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return cursor; + } +} + diff --git a/src/main/java/com/ryanmichela/sshd/ConsoleCommandFactory.java b/src/main/java/com/ryanmichela/sshd/ConsoleCommandFactory.java new file mode 100644 index 0000000..32c093b --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/ConsoleCommandFactory.java @@ -0,0 +1,67 @@ +package com.ryanmichela.sshd; + +import org.apache.sshd.server.Command; +import org.apache.sshd.server.CommandFactory; +import org.apache.sshd.server.Environment; +import org.apache.sshd.server.ExitCallback; +import org.bukkit.Bukkit; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copyright 2013 Ryan Michela + */ +public class ConsoleCommandFactory implements CommandFactory { + @Override + public Command createCommand(String command) { + return new ConsoleCommand(command); + } + + public class ConsoleCommand implements Command { + private String command; + + private InputStream in; + private OutputStream out; + private OutputStream err; + private ExitCallback callback; + + public ConsoleCommand(String command) { + this.command = command; + } + + public void setInputStream(InputStream in) { + this.in = in; + } + + public void setOutputStream(OutputStream out) { + this.out = out; + } + + public void setErrorStream(OutputStream err) { + this.err = err; + } + + public void setExitCallback(ExitCallback callback) { + this.callback = callback; + } + + @Override + public void start(Environment environment) throws IOException { + try { + SshdPlugin.instance.getLogger().info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); + } catch (Exception e) { + SshdPlugin.instance.getLogger().severe("Error processing command from SSH"); + } finally { + callback.onExit(0); + } + } + + @Override + public void destroy() { + + } + } +} diff --git a/src/main/java/com/ryanmichela/sshd/ConsoleShellFactory.java b/src/main/java/com/ryanmichela/sshd/ConsoleShellFactory.java new file mode 100644 index 0000000..7987c63 --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/ConsoleShellFactory.java @@ -0,0 +1,128 @@ +package com.ryanmichela.sshd; + +import org.apache.sshd.common.Factory; +import org.apache.sshd.server.Command; +import org.apache.sshd.server.Environment; +import org.apache.sshd.server.ExitCallback; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.libs.jline.console.ConsoleReader; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Formatter; +import java.util.logging.Logger; +import java.util.logging.StreamHandler; + +public class ConsoleShellFactory implements Factory { + + public Command create() { + return new ConsoleShell(); + } + + 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; + + StreamHandler streamHandler; + ConsoleReader consoleReader; + + public InputStream getIn() { + return in; + } + + public OutputStream getOut() { + return out; + } + + public OutputStream getErr() { + return err; + } + + public Environment getEnvironment() { + return environment; + } + + public void setInputStream(InputStream in) { + this.in = in; + } + + public void setOutputStream(OutputStream out) { + this.out = out; + } + + public void setErrorStream(OutputStream err) { + this.err = err; + } + + public void setExitCallback(ExitCallback callback) { + this.callback = callback; + } + + public void start(Environment env) throws IOException { + + Formatter bukkitFormatter = Bukkit.getLogger().getHandlers()[0].getFormatter(); + + try { + consoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal()); + consoleReader.setExpandEvents(true); + consoleReader.addCompleter(new ConsoleCommandCompleter()); + + streamHandler = new FlushyStreamHandler(out, bukkitFormatter, consoleReader); + Bukkit.getLogger().addHandler(streamHandler); + Logger.getLogger("").addHandler(streamHandler); + + environment = env; + thread = new Thread(this, "EchoShell " + env.getEnv().get(Environment.ENV_USER)); + thread.start(); + } catch (Exception e) { + throw new IOException("Error starting shell", e); + } + } + + public void destroy() { + Bukkit.getLogger().removeHandler(streamHandler); + Logger.getLogger("").removeHandler(streamHandler); + } + + public void run() { + String command; + try { + printPreamble(consoleReader); + while(true) { + command = consoleReader.readLine("\r>", null); + if (command != null) { + if (command.equals("exit")) { + break; + } + SshdPlugin.instance.getLogger().info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); + } + } + } catch (IOException e) { + SshdPlugin.instance.getLogger().severe("Error processing command from SSH"); + } finally { + callback.onExit(0); + } + } + + private void printPreamble(ConsoleReader consoleReader) throws IOException{ + consoleReader.println(" _____ _____ _ _ _____"); + consoleReader.println(" / ____/ ____| | | | __ \\"); + consoleReader.println("| (___| (___ | |__| | | | |"); + consoleReader.println(" \\___ \\\\___ \\| __ | | | |"); + consoleReader.println(" ____) |___) | | | | |__| |"); + consoleReader.println("|_____/_____/|_| |_|_____/"); + consoleReader.println("Connected to: " + Bukkit.getServer().getName()); + consoleReader.println("- " + Bukkit.getServer().getMotd()); + consoleReader.println(); + consoleReader.println("Type 'exit' to exit the shell."); + consoleReader.println("==============================================="); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ryanmichela/sshd/FlushyOutputStream.java b/src/main/java/com/ryanmichela/sshd/FlushyOutputStream.java new file mode 100644 index 0000000..1aea21e --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/FlushyOutputStream.java @@ -0,0 +1,33 @@ +package com.ryanmichela.sshd; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Copyright 2013 Ryan Michela + */ +public class FlushyOutputStream extends OutputStream { + private OutputStream base; + + public FlushyOutputStream(OutputStream base) { + this.base = base; + } + + @Override + public void write(int b) throws IOException { + base.write(b); + base.flush(); + } + + @Override + public void write(byte[] b) throws IOException { + base.write(b); + base.flush(); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + base.write(b, off, len); + base.flush(); + } +} diff --git a/src/main/java/com/ryanmichela/sshd/FlushyStreamHandler.java b/src/main/java/com/ryanmichela/sshd/FlushyStreamHandler.java new file mode 100644 index 0000000..8c900f2 --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/FlushyStreamHandler.java @@ -0,0 +1,48 @@ +package com.ryanmichela.sshd; + +import org.apache.sshd.common.SshException; +import org.bukkit.craftbukkit.libs.jline.console.ConsoleReader; + +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/PemDecoder.java b/src/main/java/com/ryanmichela/sshd/PemDecoder.java new file mode 100644 index 0000000..1dc9424 --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/PemDecoder.java @@ -0,0 +1,97 @@ +package com.ryanmichela.sshd; + +import org.apache.mina.util.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 { + StringBuffer b64 = new StringBuffer(); + + 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); + } +} diff --git a/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java b/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java new file mode 100644 index 0000000..d8eb328 --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java @@ -0,0 +1,50 @@ +package com.ryanmichela.sshd; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.sshd.server.PublickeyAuthenticator; +import org.apache.sshd.server.session.ServerSession; + +import java.io.File; +import java.io.FileReader; +import java.security.PublicKey; + +/** + * Copyright 2013 Ryan Michela + */ +public class PublicKeyAuthenticator implements PublickeyAuthenticator { + private File 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); + + if (keyFile.exists()) { + try { + + 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()); + } + } 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; + } +} diff --git a/src/main/java/com/ryanmichela/sshd/ReflectionUtil.java b/src/main/java/com/ryanmichela/sshd/ReflectionUtil.java new file mode 100644 index 0000000..f5d73c3 --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/ReflectionUtil.java @@ -0,0 +1,98 @@ +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 ex) { + System.out.println("*** " + c.getName() + ":" + ex); + } catch (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/SshTerminal.java b/src/main/java/com/ryanmichela/sshd/SshTerminal.java new file mode 100644 index 0000000..a777324 --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/SshTerminal.java @@ -0,0 +1,19 @@ +package com.ryanmichela.sshd; + +import org.bukkit.craftbukkit.libs.jline.TerminalSupport; + +/** + * Copyright 2013 Ryan Michela + */ +public class SshTerminal extends TerminalSupport { + + protected SshTerminal() { + super(true); + } + + @Override + public void init() throws Exception { + setAnsiSupported(true); + setEchoEnabled(true); + } +} diff --git a/src/main/java/com/ryanmichela/sshd/SshdPlugin.java b/src/main/java/com/ryanmichela/sshd/SshdPlugin.java new file mode 100644 index 0000000..30e911b --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/SshdPlugin.java @@ -0,0 +1,62 @@ +package com.ryanmichela.sshd; + +import org.apache.sshd.SshServer; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; + +/** + * Copyright 2013 Ryan Michela + */ +public class SshdPlugin extends JavaPlugin { + private SshServer sshd; + public static SshdPlugin instance; + + @Override + public void onLoad() { + 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. + // SSHD will log that it wrote bites to the output stream, which writes + // bytes to the output stream - ad nauseaum. + getLogger().setLevel(Level.INFO); + } + + @Override + public void onEnable() { + instance = this; + + sshd = SshServer.setUpDefaultServer(); + sshd.setPort(getConfig().getInt("port", 22)); + + File hostKey = new File(getDataFolder(), "hostkey"); + File authorizedKeys = new File(getDataFolder(), "authorized_keys"); + + sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey.getPath())); + sshd.setShellFactory(new ConsoleShellFactory()); + sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator()); + sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys)); + sshd.setCommandFactory(new ConsoleCommandFactory()); + try { + sshd.start(); + } catch (IOException e) { + getLogger().severe("Failed to start SSH server! " + e.getMessage()); + } + } + + @Override + public void onDisable() { + try { + sshd.stop(); + } catch (InterruptedException e) { + // do nothing + } + } +} diff --git a/src/main/java/com/ryanmichela/sshd/Waitable.java b/src/main/java/com/ryanmichela/sshd/Waitable.java new file mode 100644 index 0000000..64f0cc3 --- /dev/null +++ b/src/main/java/com/ryanmichela/sshd/Waitable.java @@ -0,0 +1,48 @@ +package com.ryanmichela.sshd; + +import java.util.concurrent.ExecutionException; + +/** + * Copyright 2013 Ryan Michela + */ +public abstract class Waitable implements Runnable { + private enum Status { + WAITING, + RUNNING, + FINISHED, + } + Throwable t = null; + T value = null; + Status status = Status.WAITING; + + public final void run() { + synchronized (this) { + if (status != Status.WAITING) { + throw new IllegalStateException("Invalid state " + status); + } + status = Status.RUNNING; + } + try { + value = evaluate(); + } catch (Throwable t) { + this.t = t; + } finally { + synchronized (this) { + status = Status.FINISHED; + this.notifyAll(); + } + } + } + + protected abstract T evaluate(); + + public synchronized T get() throws InterruptedException, ExecutionException { + while (status != Status.FINISHED) { + this.wait(); + } + if (t != null) { + throw new ExecutionException(t); + } + return value; + } +} diff --git a/src/main/java/org/slf4j/impl/PluginSlf4jFactory.java b/src/main/java/org/slf4j/impl/PluginSlf4jFactory.java new file mode 100644 index 0000000..67c3e1c --- /dev/null +++ b/src/main/java/org/slf4j/impl/PluginSlf4jFactory.java @@ -0,0 +1,353 @@ +package org.slf4j.impl; + +import com.ryanmichela.sshd.SshdPlugin; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.Marker; +import org.slf4j.helpers.FormattingTuple; +import org.slf4j.helpers.MessageFormatter; + +import java.util.logging.Level; + +/** + * Copyright 2013 Ryan Michela + */ +public class PluginSlf4jFactory implements ILoggerFactory { + @Override + public Logger getLogger(String name) { + return new PluginSlf4jAdapter(name); + } + + public class PluginSlf4jAdapter implements Logger { + private String name; + + private boolean isEnabled(Level level) { + if (SshdPlugin.instance != null) { + return SshdPlugin.instance.getLogger().isLoggable(level); + } + return false; + } + + private void log(Level level, String s, Object[] objects) { + if (SshdPlugin.instance != null && isEnabled(level)) { + FormattingTuple ft = MessageFormatter.arrayFormat(s, objects); + SshdPlugin.instance.getLogger().log(level, ft.getMessage(), ft.getThrowable()); + } + } + + private void log(Level level, String s, Throwable throwable) { + if (SshdPlugin.instance != null && isEnabled(level)) { + SshdPlugin.instance.getLogger().log(level, s, throwable); + } + } + + public PluginSlf4jAdapter(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isTraceEnabled() { + return isEnabled(Level.FINEST); + } + + @Override + public void trace(String s) { + trace(s, new Object[]{}); + } + + @Override + public void trace(String s, Object o) { + trace(s, new Object[]{o}); + } + + @Override + public void trace(String s, Object o, Object o1) { + trace(s, new Object[]{o, o1}); + } + + @Override + public void trace(String s, Object[] objects) { + log(Level.FINEST, s, objects); + } + + @Override + public void trace(String s, Throwable throwable) { + log(Level.FINEST, s, throwable); + } + + @Override + public boolean isTraceEnabled(Marker marker) { + return isTraceEnabled(); + } + + @Override + public void trace(Marker marker, String s) { + trace(s); + } + + @Override + public void trace(Marker marker, String s, Object o) { + trace(s, o); + } + + @Override + public void trace(Marker marker, String s, Object o, Object o1) { + trace(s, o, o1); + } + + @Override + public void trace(Marker marker, String s, Object[] objects) { + trace(s, objects); + } + + @Override + public void trace(Marker marker, String s, Throwable throwable) { + trace(s, throwable); + } + + @Override + public boolean isDebugEnabled() { + return isEnabled(Level.FINE); + } + + @Override + public void debug(String s) { + debug(s, new Object[]{}); + } + + @Override + public void debug(String s, Object o) { + debug(s, new Object[]{o}); + } + + @Override + public void debug(String s, Object o, Object o1) { + debug(s, new Object[]{o, o1}); + } + + @Override + public void debug(String s, Object[] objects) { + log(Level.FINE, s, objects); + } + + @Override + public void debug(String s, Throwable throwable) { + log(Level.FINE, s, throwable); + } + + @Override + public boolean isDebugEnabled(Marker marker) { + return isDebugEnabled(); + } + + @Override + public void debug(Marker marker, String s) { + debug(s); + } + + @Override + public void debug(Marker marker, String s, Object o) { + debug(s, o); + } + + @Override + public void debug(Marker marker, String s, Object o, Object o1) { + debug(s, o, o1); + } + + @Override + public void debug(Marker marker, String s, Object[] objects) { + debug(s, objects); + } + + @Override + public void debug(Marker marker, String s, Throwable throwable) { + debug(s, throwable); + } + + @Override + public boolean isInfoEnabled() { + return isEnabled(Level.INFO); + } + + @Override + public void info(String s) { + info(s, new Object[]{}); + } + + @Override + public void info(String s, Object o) { + info(s, new Object[]{o}); + } + + @Override + public void info(String s, Object o, Object o1) { + info(s, new Object[]{o, o1}); + } + + @Override + public void info(String s, Object[] objects) { + log(Level.INFO, s, objects); + } + + @Override + public void info(String s, Throwable throwable) { + log(Level.INFO, s, throwable); + } + + @Override + public boolean isInfoEnabled(Marker marker) { + return isInfoEnabled(); + } + + @Override + public void info(Marker marker, String s) { + info(s); + } + + @Override + public void info(Marker marker, String s, Object o) { + info(s, o); + } + + @Override + public void info(Marker marker, String s, Object o, Object o1) { + info(s, o, o1); + } + + @Override + public void info(Marker marker, String s, Object[] objects) { + info(s, objects); + } + + @Override + public void info(Marker marker, String s, Throwable throwable) { + info(s, throwable); + } + + @Override + public boolean isWarnEnabled() { + return isEnabled(Level.WARNING); + } + + @Override + public void warn(String s) { + warn(s, new Object[]{}); + } + + @Override + public void warn(String s, Object o) { + warn(s, new Object[]{o}); + } + + @Override + public void warn(String s, Object o, Object o1) { + warn(s, new Object[]{o, o1}); + } + + @Override + public void warn(String s, Object[] objects) { + log(Level.WARNING, s, objects); + } + + @Override + public void warn(String s, Throwable throwable) { + log(Level.WARNING, s, throwable); + } + + @Override + public boolean isWarnEnabled(Marker marker) { + return isWarnEnabled(); + } + + @Override + public void warn(Marker marker, String s) { + warn(s); + } + + @Override + public void warn(Marker marker, String s, Object o) { + warn(s, o); + } + + @Override + public void warn(Marker marker, String s, Object o, Object o1) { + warn(s, o, o1); + } + + @Override + public void warn(Marker marker, String s, Object[] objects) { + warn(s, objects); + } + + @Override + public void warn(Marker marker, String s, Throwable throwable) { + warn(s, throwable); + } + + @Override + public boolean isErrorEnabled() { + return isEnabled(Level.SEVERE); + } + + @Override + public void error(String s) { + error(s, new Object[]{}); + } + + @Override + public void error(String s, Object o) { + error(s, new Object[]{o}); + } + + @Override + public void error(String s, Object o, Object o1) { + error(s, new Object[]{o, o1}); + } + + @Override + public void error(String s, Object[] objects) { + log(Level.SEVERE, s, objects); + } + + @Override + public void error(String s, Throwable throwable) { + log(Level.SEVERE, s, throwable); + } + + @Override + public boolean isErrorEnabled(Marker marker) { + return isErrorEnabled(); + } + + @Override + public void error(Marker marker, String s) { + error(s); + } + + @Override + public void error(Marker marker, String s, Object o) { + error(s, o); + } + + @Override + public void error(Marker marker, String s, Object o, Object o1) { + error(s, o, o1); + } + + @Override + public void error(Marker marker, String s, Object[] objects) { + error(s, objects); + } + + @Override + public void error(Marker marker, String s, Throwable throwable) { + error(s, throwable); + } + } +} diff --git a/src/main/java/org/slf4j/impl/StaticLoggerBinder.java b/src/main/java/org/slf4j/impl/StaticLoggerBinder.java new file mode 100644 index 0000000..b3b10fd --- /dev/null +++ b/src/main/java/org/slf4j/impl/StaticLoggerBinder.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2004-2011 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j.impl; + +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; +import org.slf4j.spi.LoggerFactoryBinder; + +/** + * The binding of {@link LoggerFactory} class with an actual instance of + * {@link ILoggerFactory} is performed using information returned by this class. + * + * @author Ceki Gülcü + */ +public class StaticLoggerBinder implements LoggerFactoryBinder { + + /** + * The unique instance of this class. + * + */ + private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); + + /** + * Return the singleton of this class. + * + * @return the StaticLoggerBinder singleton + */ + public static final StaticLoggerBinder getSingleton() { + return SINGLETON; + } + + + /** + * Declare the version of the SLF4J API this implementation is compiled against. + * The value of this field is usually modified with each release. + */ + // to avoid constant folding by the compiler, this field must *not* be final + public static String REQUESTED_API_VERSION = "1.6.99"; // !final + + + private static final String loggerFactoryClassStr = PluginSlf4jFactory.class.getName(); + + /** The ILoggerFactory instance returned by the {@link #getLoggerFactory} method + * should always be the same object + */ + private final ILoggerFactory loggerFactory; + + private StaticLoggerBinder() { +// Note: JCL gets substituted at build time by an appropriate Ant task + loggerFactory = new PluginSlf4jFactory(); + } + + public ILoggerFactory getLoggerFactory() { + return loggerFactory; + } + + public String getLoggerFactoryClassStr() { + return loggerFactoryClassStr; + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..1c29280 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,12 @@ +# This is the port the SSH server will listen on. +port: 22 + +# 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). + +# For less secure username and password based authentication, complete the sections below. +credentials: +# user1: password1 +# user2: password2 \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..46fa723 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,4 @@ +name: SSHD +version: "1.0" +author: Ryan Michela +main: com.ryanmichela.sshd.SshdPlugin \ No newline at end of file