Fixed a bug (open on the upstream fork), also rewote config.
Fixed a bug that caused sessions to get overwritten and some of them would seem to freeze, the whole thing relied on undefined behavior. This bug was a static variable that copied sessions all around globally. Rewrote the config to support a few more options (the PasswordType is coming soon) and explained how the new authorized_users files work. Public key authentication now has the same number of retires that password authentication has (this aligns with how OpenSSH does it) and the number of retries can now be configured in the configuration.
This commit is contained in:
parent
25287b1580
commit
0635ea7a35
2
pom.xml
2
pom.xml
@ -56,6 +56,8 @@
|
|||||||
<groupId>org.apache.sshd</groupId>
|
<groupId>org.apache.sshd</groupId>
|
||||||
<artifactId>sshd-common</artifactId>
|
<artifactId>sshd-common</artifactId>
|
||||||
<version>2.3.0</version>
|
<version>2.3.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<type>jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -11,28 +11,36 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
|
public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
|
||||||
|
|
||||||
private Map<String, Integer> failCounts = new HashMap<String, Integer>();
|
private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean authenticate(String username, String password, ServerSession serverSession) {
|
public boolean authenticate(String username, String password, ServerSession ss)
|
||||||
if (SshdPlugin.instance.getConfig().getString("credentials." + username).equals(password)) {
|
{
|
||||||
failCounts.put(username, 0);
|
if (SshdPlugin.instance.getConfig().getString("Credentials." + username).equals(password))
|
||||||
|
{
|
||||||
|
FailCounts.put(username, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using password authentication.");
|
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using password authentication.");
|
||||||
|
|
||||||
try {
|
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
Thread.sleep(3000);
|
Thread.sleep(3000);
|
||||||
if (failCounts.containsKey(username)) {
|
if (this.FailCounts.containsKey(username))
|
||||||
failCounts.put(username, failCounts.get(username) + 1);
|
this.FailCounts.put(username, this.FailCounts.get(username) + 1);
|
||||||
} else {
|
else
|
||||||
failCounts.put(username, 1);
|
this.FailCounts.put(username, 1);
|
||||||
|
|
||||||
|
if (this.FailCounts.get(username) >= tries)
|
||||||
|
{
|
||||||
|
this.FailCounts.put(username, 0);
|
||||||
|
ss.close(true);
|
||||||
}
|
}
|
||||||
if (failCounts.get(username) >= 3) {
|
|
||||||
failCounts.put(username, 0);
|
|
||||||
serverSession.close(true);
|
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -27,13 +27,11 @@ import java.util.logging.StreamHandler;
|
|||||||
|
|
||||||
public class ConsoleShellFactory implements ShellFactory {
|
public class ConsoleShellFactory implements ShellFactory {
|
||||||
|
|
||||||
static SSHDCommandSender sshdCommandSender = new SSHDCommandSender();
|
|
||||||
|
|
||||||
public Command createShell(ChannelSession cs) {
|
public Command createShell(ChannelSession cs) {
|
||||||
return new ConsoleShell();
|
return new ConsoleShell();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ConsoleShell implements Command, Runnable {
|
public class ConsoleShell implements Command, Runnable {
|
||||||
|
|
||||||
private InputStream in;
|
private InputStream in;
|
||||||
private OutputStream out;
|
private OutputStream out;
|
||||||
@ -41,9 +39,11 @@ public class ConsoleShellFactory implements ShellFactory {
|
|||||||
private ExitCallback callback;
|
private ExitCallback callback;
|
||||||
private Environment environment;
|
private Environment environment;
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
|
private String Username;
|
||||||
|
|
||||||
StreamHandlerAppender streamHandlerAppender;
|
StreamHandlerAppender streamHandlerAppender;
|
||||||
public static ConsoleReader consoleReader;
|
public ConsoleReader ConsoleReader;
|
||||||
|
public SSHDCommandSender SshdCommandSender;
|
||||||
|
|
||||||
public InputStream getIn() {
|
public InputStream getIn() {
|
||||||
return in;
|
return in;
|
||||||
@ -82,17 +82,20 @@ public class ConsoleShellFactory implements ShellFactory {
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
consoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal());
|
this.ConsoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal());
|
||||||
consoleReader.setExpandEvents(true);
|
this.ConsoleReader.setExpandEvents(true);
|
||||||
consoleReader.addCompleter(new ConsoleCommandCompleter());
|
this.ConsoleReader.addCompleter(new ConsoleCommandCompleter());
|
||||||
|
|
||||||
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), consoleReader);
|
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader);
|
||||||
streamHandlerAppender = new StreamHandlerAppender(streamHandler);
|
streamHandlerAppender = new StreamHandlerAppender(streamHandler);
|
||||||
|
|
||||||
((Logger)LogManager.getRootLogger()).addAppender(streamHandlerAppender);
|
((Logger)LogManager.getRootLogger()).addAppender(streamHandlerAppender);
|
||||||
|
|
||||||
environment = env;
|
this.environment = env;
|
||||||
thread = new Thread(this, "SSHD ConsoleShell " + env.getEnv().get(Environment.ENV_USER));
|
this.Username = env.getEnv().get(Environment.ENV_USER);
|
||||||
|
this.SshdCommandSender = new SSHDCommandSender();
|
||||||
|
this.SshdCommandSender.console = this;
|
||||||
|
thread = new Thread(this, "SSHD ConsoleShell " + this.Username);
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -108,28 +111,28 @@ public class ConsoleShellFactory implements ShellFactory {
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!SshdPlugin.instance.getConfig().getString("mode").equals("RPC"))
|
if (!SshdPlugin.instance.getConfig().getString("Mode").equals("RPC"))
|
||||||
printPreamble(consoleReader);
|
printPreamble(this.ConsoleReader);
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
String command = consoleReader.readLine("\r>", null);
|
String command = this.ConsoleReader.readLine("\r>", null);
|
||||||
if (command == null)
|
if (command == null || command.trim().isEmpty())
|
||||||
continue;
|
continue;
|
||||||
if (command.equals("exit") || command.equals("quit"))
|
if (command.equals("exit") || command.equals("quit"))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
Bukkit.getScheduler().runTask(
|
Bukkit.getScheduler().runTask(
|
||||||
SshdPlugin.instance, () ->
|
SshdPlugin.instance, () ->
|
||||||
{
|
{
|
||||||
if (SshdPlugin.instance.getConfig().getString("mode").equals("RPC") && command.startsWith("rpc"))
|
if (SshdPlugin.instance.getConfig().getString("Mode").equals("RPC") && command.startsWith("rpc"))
|
||||||
{
|
{
|
||||||
// NO ECHO NO PREAMBLE AND SHIT
|
// NO ECHO NO PREAMBLE AND SHIT
|
||||||
String cmd = command.substring("rpc".length() + 1, command.length());
|
String cmd = command.substring("rpc".length() + 1, command.length());
|
||||||
Bukkit.dispatchCommand(sshdCommandSender, cmd);
|
Bukkit.dispatchCommand(this.SshdCommandSender, cmd);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SshdPlugin.instance.getLogger().info("<" + environment.getEnv().get(Environment.ENV_USER) + "> "
|
SshdPlugin.instance.getLogger().info("<" + this.Username + "> " + command);
|
||||||
+ command);
|
|
||||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -145,19 +148,19 @@ public class ConsoleShellFactory implements ShellFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printPreamble(ConsoleReader consoleReader) throws IOException
|
private void printPreamble(ConsoleReader cr) throws IOException
|
||||||
{
|
{
|
||||||
consoleReader.println(" _____ _____ _ _ _____" + "\r");
|
cr.println(" _____ _____ _ _ _____" + "\r");
|
||||||
consoleReader.println(" / ____/ ____| | | | __ \\" + "\r");
|
cr.println(" / ____/ ____| | | | __ \\" + "\r");
|
||||||
consoleReader.println("| (___| (___ | |__| | | | |" + "\r");
|
cr.println("| (___| (___ | |__| | | | |" + "\r");
|
||||||
consoleReader.println(" \\___ \\\\___ \\| __ | | | |" + "\r");
|
cr.println(" \\___ \\\\___ \\| __ | | | |" + "\r");
|
||||||
consoleReader.println(" ____) |___) | | | | |__| |" + "\r");
|
cr.println(" ____) |___) | | | | |__| |" + "\r");
|
||||||
consoleReader.println("|_____/_____/|_| |_|_____/" + "\r");
|
cr.println("|_____/_____/|_| |_|_____/" + "\r");
|
||||||
consoleReader.println("Connected to: " + Bukkit.getServer().getName() + "\r");
|
cr.println("Connected to: " + Bukkit.getServer().getName() + "\r");
|
||||||
consoleReader.println("- " + Bukkit.getServer().getMotd() + "\r");
|
cr.println("- " + Bukkit.getServer().getMotd() + "\r");
|
||||||
consoleReader.println("\r");
|
cr.println("\r");
|
||||||
consoleReader.println("Type 'exit' to exit the shell." + "\r");
|
cr.println("Type 'exit' to exit the shell." + "\r");
|
||||||
consoleReader.println("===============================================" + "\r");
|
cr.println("===============================================" + "\r");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,6 +8,8 @@ import org.apache.sshd.server.session.ServerSession;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
|
||||||
@ -16,8 +18,8 @@ import java.security.PublicKey;
|
|||||||
*/
|
*/
|
||||||
public class PublicKeyAuthenticator implements PublickeyAuthenticator
|
public class PublicKeyAuthenticator implements PublickeyAuthenticator
|
||||||
{
|
{
|
||||||
|
|
||||||
private File authorizedKeysDir;
|
private File authorizedKeysDir;
|
||||||
|
private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
|
||||||
|
|
||||||
public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
|
public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
|
||||||
|
|
||||||
@ -25,48 +27,45 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator
|
|||||||
{
|
{
|
||||||
byte[] keyBytes = key.getEncoded();
|
byte[] keyBytes = key.getEncoded();
|
||||||
File keyFile = new File(authorizedKeysDir, username);
|
File keyFile = new File(authorizedKeysDir, username);
|
||||||
|
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries");
|
||||||
|
|
||||||
if (keyFile.exists())
|
if (keyFile.exists())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Read all the public key entries
|
||||||
List<AuthorizedKeyEntry> pklist = AuthorizedKeyEntry.readAuthorizedKeys(keyFile.toPath());
|
List<AuthorizedKeyEntry> pklist = AuthorizedKeyEntry.readAuthorizedKeys(keyFile.toPath());
|
||||||
|
// Get an authenticator
|
||||||
PublickeyAuthenticator auth = PublickeyAuthenticator.fromAuthorizedEntries(username, session, pklist,
|
PublickeyAuthenticator auth = PublickeyAuthenticator.fromAuthorizedEntries(username, session, pklist,
|
||||||
PublicKeyEntryResolver.IGNORING);
|
PublicKeyEntryResolver.IGNORING);
|
||||||
|
|
||||||
boolean accepted = auth.authenticate(username, key, session);
|
// Validate that the logging in user has the same valid SSH key
|
||||||
|
if (auth.authenticate(username, key, session))
|
||||||
if (accepted)
|
|
||||||
{
|
{
|
||||||
SshdPlugin.instance.getLogger().info(
|
SshdPlugin.instance.getLogger().info(
|
||||||
username + " successfully authenticated via SSH session using key file " + keyFile.getAbsolutePath());
|
username + " successfully authenticated via SSH session using key file " + keyFile.getAbsolutePath());
|
||||||
|
FailCounts.put(username, 0);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SshdPlugin.instance.getLogger().info(
|
SshdPlugin.instance.getLogger().info(
|
||||||
username + " failed authentication via SSH session using key file " + keyFile.getAbsolutePath());
|
username + " failed authentication via SSH session using key file " + keyFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
return accepted;
|
|
||||||
/*
|
|
||||||
|
|
||||||
FileReader fr = new FileReader(keyFile);
|
// If the user fails with several SSH keys, then terminate the connection.
|
||||||
PemDecoder pd = new PemDecoder(fr);
|
if (this.FailCounts.containsKey(username))
|
||||||
PublicKey k = pd.getPemBytes();
|
this.FailCounts.put(username, this.FailCounts.get(username) + 1);
|
||||||
pd.close();
|
|
||||||
|
|
||||||
if (k != null)
|
|
||||||
{
|
|
||||||
if (ArrayUtils.isEquals(key.getEncoded(), k.getEncoded()))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
|
this.FailCounts.put(username, 1);
|
||||||
|
|
||||||
|
if (this.FailCounts.get(username) >= tries)
|
||||||
{
|
{
|
||||||
SshdPlugin.instance.getLogger().severe("Failed to parse PEM file. " + keyFile.getAbsolutePath());
|
this.FailCounts.put(username, 0);
|
||||||
|
session.close(true);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -44,8 +44,8 @@ class SshdPlugin extends JavaPlugin
|
|||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
sshd = SshServer.setUpDefaultServer();
|
sshd = SshServer.setUpDefaultServer();
|
||||||
sshd.setPort(getConfig().getInt("port", 22));
|
sshd.setPort(getConfig().getInt("Port", 1025));
|
||||||
String host = getConfig().getString("listenAddress", "all");
|
String host = getConfig().getString("ListenAddress", "all");
|
||||||
sshd.setHost(host.equals("all") ? null : host);
|
sshd.setHost(host.equals("all") ? null : host);
|
||||||
|
|
||||||
File hostKey = new File(getDataFolder(), "hostkey");
|
File hostKey = new File(getDataFolder(), "hostkey");
|
||||||
@ -56,7 +56,7 @@ class SshdPlugin extends JavaPlugin
|
|||||||
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
|
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
|
||||||
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
|
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
|
||||||
|
|
||||||
if (getConfig().getBoolean("enableSFTP"))
|
if (getConfig().getBoolean("EnableSFTP"))
|
||||||
{
|
{
|
||||||
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
|
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
|
||||||
sshd.setFileSystemFactory(
|
sshd.setFileSystemFactory(
|
||||||
|
@ -26,16 +26,24 @@ public class SSHDCommandSender implements ConsoleCommandSender, CommandSender {
|
|||||||
|
|
||||||
private final PermissibleBase perm = new PermissibleBase(this);
|
private final PermissibleBase perm = new PermissibleBase(this);
|
||||||
private final SSHDConversationTracker conversationTracker = new SSHDConversationTracker();
|
private final SSHDConversationTracker conversationTracker = new SSHDConversationTracker();
|
||||||
|
// Set by the upstream allocating function
|
||||||
|
public ConsoleShellFactory.ConsoleShell console;
|
||||||
|
|
||||||
public void sendMessage(String message) {
|
public void sendMessage(String message) {
|
||||||
this.sendRawMessage(message);
|
this.sendRawMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendRawMessage(String message) {
|
public void sendRawMessage(String message)
|
||||||
if(ConsoleShellFactory.ConsoleShell.consoleReader == null) return;
|
{
|
||||||
try {
|
// What the fuck does this code even do? Are we sending to one client or all of them?
|
||||||
ConsoleShellFactory.ConsoleShell.consoleReader.println(ChatColor.stripColor(message));
|
if (this.console.ConsoleReader == null)
|
||||||
} catch (IOException e) {
|
return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.console.ConsoleReader.println(ChatColor.stripColor(message));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e);
|
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,7 +53,7 @@ public class SSHDCommandSender implements ConsoleCommandSender, CommandSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Console";
|
return "SSHD Console";
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOp() {
|
public boolean isOp() {
|
||||||
|
@ -1,22 +1,35 @@
|
|||||||
# The IP addresses(s) the SSH server will listen on. Use a comma separated list for multiple addresses.
|
# The IP addresses(s) the SSH server will listen on. Use a comma separated list for multiple addresses.
|
||||||
# Leave as "all" for all addresses.
|
# Leave as "all" for all addresses.
|
||||||
listenAddress: all
|
ListenAddress: all
|
||||||
# The port the SSH server will listen on.
|
# The port the SSH server will listen on. Note that anything above 1024 will require you to run
|
||||||
port: 22
|
# the whole minecraft server with elevated privileges, this is not recommended and you should
|
||||||
|
# use iptables to route packets from a lower port.
|
||||||
|
Port: 1025
|
||||||
|
|
||||||
# Operational mode. Don't touch if you don't know what you're doing. Can be either DEFAULT or RPC
|
# Operational mode. Don't touch if you don't know what you're doing. Can be either DEFAULT or RPC
|
||||||
mode: DEFAULT
|
Mode: DEFAULT
|
||||||
|
|
||||||
# Enable built-in SFTP server or not. You'll be able to connect and upload/download files via SFTP protocol.
|
# Enable built-in SFTP server or not. You'll be able to connect and upload/download files via SFTP protocol.
|
||||||
# Might be useful for testing purposes as well , i. e. docker containers.
|
# Might be useful for testing purposes as well , i. e. docker containers.
|
||||||
enableSFTP: true
|
EnableSFTP: true
|
||||||
|
|
||||||
|
# Number of times a person can fail to use an SSH key or enter a password
|
||||||
|
# before it terminates the connection.
|
||||||
|
LoginRetries: 3
|
||||||
|
|
||||||
# By default, only public key authentication is enabled. This is the most secure mode.
|
# 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
|
# To authorize a user to login with their public key, install their key using the
|
||||||
# PEM certificate in the authorized_users directory. Name the key file with user's user
|
# OpenSSH authorized_keys file format in the authorized_users directory. Name the key
|
||||||
# name (no file extension).
|
# file with the user's username and no extension. Note: If you want to let a user have
|
||||||
|
# many keys, you can append the keys to their file in authorized_users.
|
||||||
|
|
||||||
# For less secure username and password based authentication, complete the sections below.
|
# For less secure username and password based authentication, complete the sections below.
|
||||||
credentials:
|
|
||||||
|
# Type of hashing to use for the passwords below.
|
||||||
|
# Options are: PLAIN (insecure), bcrypt, pbkdf, sha256
|
||||||
|
PasswordType: bcrypt
|
||||||
|
|
||||||
|
# Associate each username with a password hash (or the password if the PasswordType is set to PLAIN)
|
||||||
|
Credentials:
|
||||||
# user1: password1
|
# user1: password1
|
||||||
# user2: password2
|
# user2: password2
|
||||||
|
Loading…
Reference in New Issue
Block a user