Initial commit.
This commit is contained in:
parent
101b7eb972
commit
e98113397d
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,3 +4,6 @@
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
.idea
|
||||
*.iml
|
||||
target
|
||||
|
95
pom.xml
Normal file
95
pom.xml
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.ryanmichela</groupId>
|
||||
<artifactId>SSHD</artifactId>
|
||||
<version>1.0</version>
|
||||
<url>http://dev.bukkit.org/server-mods/sshd/</url>
|
||||
|
||||
<!-- Repositories -->
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>bukkit-repo</id>
|
||||
<url>http://repo.bukkit.org/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<!-- License -->
|
||||
<licenses>
|
||||
<license>
|
||||
<name>GPL2</name>
|
||||
<url>http://www.gnu.org/licenses/gpl-2.0.html</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.6.4-R1.0</version>
|
||||
<scope>provided</scope>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-core</artifactId>
|
||||
<version>0.9.0</version>
|
||||
<scope>compile</scope>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>org.bouncycastle</groupId>-->
|
||||
<!--<artifactId>bcprov-jdk16</artifactId>-->
|
||||
<!--<version>1.46</version>-->
|
||||
<!--</dependency>-->
|
||||
</dependencies>
|
||||
|
||||
<!-- Build -->
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Shade plugin -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Compile plugin -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.0</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
<showDeprecation>true</showDeprecation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
</project>
|
@ -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<String, Integer> failCounts = new HashMap<String, Integer>();
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String username, String password, ServerSession serverSession) {
|
||||
if (SshdPlugin.instance.getConfig().getString("credentials." + username).equals(password)) {
|
||||
failCounts.put(username, 0);
|
||||
return true;
|
||||
}
|
||||
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using password authentication.");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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<CharSequence> candidates) {
|
||||
Waitable<List<String>> waitable = new Waitable<List<String>>() {
|
||||
@Override
|
||||
protected List<String> evaluate() {
|
||||
CommandMap commandMap = ReflectionUtil.getProtectedValue(Bukkit.getServer(), "commandMap");
|
||||
return commandMap.tabComplete(Bukkit.getServer().getConsoleSender(), buffer);
|
||||
}
|
||||
};
|
||||
Bukkit.getScheduler().runTask(SshdPlugin.instance, waitable);
|
||||
try {
|
||||
List<String> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
128
src/main/java/com/ryanmichela/sshd/ConsoleShellFactory.java
Normal file
128
src/main/java/com/ryanmichela/sshd/ConsoleShellFactory.java
Normal file
@ -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<Command> {
|
||||
|
||||
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("===============================================");
|
||||
}
|
||||
}
|
||||
}
|
33
src/main/java/com/ryanmichela/sshd/FlushyOutputStream.java
Normal file
33
src/main/java/com/ryanmichela/sshd/FlushyOutputStream.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
48
src/main/java/com/ryanmichela/sshd/FlushyStreamHandler.java
Normal file
48
src/main/java/com/ryanmichela/sshd/FlushyStreamHandler.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
97
src/main/java/com/ryanmichela/sshd/PemDecoder.java
Normal file
97
src/main/java/com/ryanmichela/sshd/PemDecoder.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
98
src/main/java/com/ryanmichela/sshd/ReflectionUtil.java
Normal file
98
src/main/java/com/ryanmichela/sshd/ReflectionUtil.java
Normal file
@ -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> 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> 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;
|
||||
}
|
||||
}
|
||||
}
|
19
src/main/java/com/ryanmichela/sshd/SshTerminal.java
Normal file
19
src/main/java/com/ryanmichela/sshd/SshTerminal.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
62
src/main/java/com/ryanmichela/sshd/SshdPlugin.java
Normal file
62
src/main/java/com/ryanmichela/sshd/SshdPlugin.java
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
48
src/main/java/com/ryanmichela/sshd/Waitable.java
Normal file
48
src/main/java/com/ryanmichela/sshd/Waitable.java
Normal file
@ -0,0 +1,48 @@
|
||||
package com.ryanmichela.sshd;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* Copyright 2013 Ryan Michela
|
||||
*/
|
||||
public abstract class Waitable<T> 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;
|
||||
}
|
||||
}
|
353
src/main/java/org/slf4j/impl/PluginSlf4jFactory.java
Normal file
353
src/main/java/org/slf4j/impl/PluginSlf4jFactory.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
82
src/main/java/org/slf4j/impl/StaticLoggerBinder.java
Normal file
82
src/main/java/org/slf4j/impl/StaticLoggerBinder.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
12
src/main/resources/config.yml
Normal file
12
src/main/resources/config.yml
Normal file
@ -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
|
4
src/main/resources/plugin.yml
Normal file
4
src/main/resources/plugin.yml
Normal file
@ -0,0 +1,4 @@
|
||||
name: SSHD
|
||||
version: "1.0"
|
||||
author: Ryan Michela
|
||||
main: com.ryanmichela.sshd.SshdPlugin
|
Loading…
Reference in New Issue
Block a user