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
+ 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