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:
Justin Crawford 2019-10-03 21:07:00 -07:00
parent 25287b1580
commit 0635ea7a35
No known key found for this signature in database
GPG Key ID: 0D84DEDBB8EF259C
8 changed files with 337 additions and 304 deletions

View File

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

View File

@ -11,30 +11,38 @@ 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))
return true; {
} FailCounts.put(username, 0);
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using password authentication."); return true;
}
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using password authentication.");
try { Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries");
Thread.sleep(3000);
if (failCounts.containsKey(username)) { try
failCounts.put(username, failCounts.get(username) + 1); {
} else { Thread.sleep(3000);
failCounts.put(username, 1); if (this.FailCounts.containsKey(username))
} this.FailCounts.put(username, this.FailCounts.get(username) + 1);
if (failCounts.get(username) >= 3) { else
failCounts.put(username, 0); this.FailCounts.put(username, 1);
serverSession.close(true);
} if (this.FailCounts.get(username) >= tries)
} catch (InterruptedException e) { {
// do nothing this.FailCounts.put(username, 0);
} ss.close(true);
return false; }
} }
catch (InterruptedException e)
{
// do nothing
}
return false;
}
} }

View File

@ -27,137 +27,140 @@ 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) {
return new ConsoleShell();
}
public Command createShell(ChannelSession cs) { public class ConsoleShell implements Command, Runnable {
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;
private String Username;
private InputStream in; StreamHandlerAppender streamHandlerAppender;
private OutputStream out; public ConsoleReader ConsoleReader;
private OutputStream err; public SSHDCommandSender SshdCommandSender;
private ExitCallback callback;
private Environment environment;
private Thread thread;
StreamHandlerAppender streamHandlerAppender; public InputStream getIn() {
public static ConsoleReader consoleReader; return in;
}
public InputStream getIn() { public OutputStream getOut() {
return in; return out;
} }
public OutputStream getOut() { public OutputStream getErr() {
return out; return err;
} }
public OutputStream getErr() { public Environment getEnvironment() {
return err; return environment;
} }
public Environment getEnvironment() { public void setInputStream(InputStream in) {
return environment; this.in = in;
} }
public void setInputStream(InputStream in) { public void setOutputStream(OutputStream out) {
this.in = in; this.out = out;
} }
public void setOutputStream(OutputStream out) { public void setErrorStream(OutputStream err) {
this.out = out; this.err = err;
} }
public void setErrorStream(OutputStream err) { public void setExitCallback(ExitCallback callback) {
this.err = err; this.callback = callback;
} }
public void setExitCallback(ExitCallback callback) { @Override
this.callback = callback;
}
@Override
public void start(ChannelSession cs, Environment env) throws IOException public void start(ChannelSession cs, Environment env) throws IOException
{ {
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);
thread.start(); this.SshdCommandSender = new SSHDCommandSender();
} this.SshdCommandSender.console = this;
catch (Exception e) thread = new Thread(this, "SSHD ConsoleShell " + this.Username);
{ thread.start();
throw new IOException("Error starting shell", e); }
} catch (Exception e)
} {
throw new IOException("Error starting shell", e);
}
}
@Override @Override
public void destroy(ChannelSession cs) { ((Logger)LogManager.getRootLogger()).removeAppender(streamHandlerAppender); } public void destroy(ChannelSession cs) { ((Logger)LogManager.getRootLogger()).removeAppender(streamHandlerAppender); }
public void run() public void run()
{ {
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(
SshdPlugin.instance, () ->
{
if (SshdPlugin.instance.getConfig().getString("mode").equals("RPC") && command.startsWith("rpc"))
{
// NO ECHO NO PREAMBLE AND SHIT
String cmd = command.substring("rpc".length() + 1, command.length());
Bukkit.dispatchCommand(sshdCommandSender, cmd);
}
else
{
SshdPlugin.instance.getLogger().info("<" + environment.getEnv().get(Environment.ENV_USER) + "> "
+ command);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
}
});
}
}
catch (IOException e)
{
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
}
finally
{
callback.onExit(0);
}
}
private void printPreamble(ConsoleReader consoleReader) throws IOException Bukkit.getScheduler().runTask(
{ SshdPlugin.instance, () ->
consoleReader.println(" _____ _____ _ _ _____" + "\r"); {
consoleReader.println(" / ____/ ____| | | | __ \\" + "\r"); if (SshdPlugin.instance.getConfig().getString("Mode").equals("RPC") && command.startsWith("rpc"))
consoleReader.println("| (___| (___ | |__| | | | |" + "\r"); {
consoleReader.println(" \\___ \\\\___ \\| __ | | | |" + "\r"); // NO ECHO NO PREAMBLE AND SHIT
consoleReader.println(" ____) |___) | | | | |__| |" + "\r"); String cmd = command.substring("rpc".length() + 1, command.length());
consoleReader.println("|_____/_____/|_| |_|_____/" + "\r"); Bukkit.dispatchCommand(this.SshdCommandSender, cmd);
consoleReader.println("Connected to: " + Bukkit.getServer().getName() + "\r"); }
consoleReader.println("- " + Bukkit.getServer().getMotd() + "\r"); else
consoleReader.println("\r"); {
consoleReader.println("Type 'exit' to exit the shell." + "\r"); SshdPlugin.instance.getLogger().info("<" + this.Username + "> " + command);
consoleReader.println("===============================================" + "\r"); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
} }
});
}
}
catch (IOException e)
{
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
}
finally
{
callback.onExit(0);
}
}
private void printPreamble(ConsoleReader cr) throws IOException
{
cr.println(" _____ _____ _ _ _____" + "\r");
cr.println(" / ____/ ____| | | | __ \\" + "\r");
cr.println("| (___| (___ | |__| | | | |" + "\r");
cr.println(" \\___ \\\\___ \\| __ | | | |" + "\r");
cr.println(" ____) |___) | | | | |__| |" + "\r");
cr.println("|_____/_____/|_| |_|_____/" + "\r");
cr.println("Connected to: " + Bukkit.getServer().getName() + "\r");
cr.println("- " + Bukkit.getServer().getMotd() + "\r");
cr.println("\r");
cr.println("Type 'exit' to exit the shell." + "\r");
cr.println("===============================================" + "\r");
}
} }
} }

View File

@ -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,57 +18,54 @@ import java.security.PublicKey;
*/ */
public class PublicKeyAuthenticator implements PublickeyAuthenticator public class PublicKeyAuthenticator implements PublickeyAuthenticator
{ {
private File authorizedKeysDir;
private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
private File authorizedKeysDir; public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
@Override public boolean authenticate(String username, PublicKey key, ServerSession session) @Override public boolean authenticate(String username, PublicKey key, ServerSession session)
{ {
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)
{ {

View File

@ -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(

View File

@ -24,108 +24,116 @@ import java.util.logging.Level;
public class SSHDCommandSender implements ConsoleCommandSender, CommandSender { 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;
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e); try
} {
} this.console.ConsoleReader.println(ChatColor.stripColor(message));
}
catch (IOException e)
{
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e);
}
}
public void sendMessage(String[] messages) { public void sendMessage(String[] messages) {
Arrays.asList(messages).forEach(this::sendMessage); Arrays.asList(messages).forEach(this::sendMessage);
} }
public String getName() { public String getName() {
return "Console"; return "SSHD Console";
} }
public boolean isOp() { public boolean isOp() {
return true; return true;
} }
public void setOp(boolean value) { public void setOp(boolean value) {
throw new UnsupportedOperationException("Cannot change operator status of server console"); throw new UnsupportedOperationException("Cannot change operator status of server console");
} }
public boolean beginConversation(Conversation conversation) { public boolean beginConversation(Conversation conversation) {
return this.conversationTracker.beginConversation(conversation); return this.conversationTracker.beginConversation(conversation);
} }
public void abandonConversation(Conversation conversation) { public void abandonConversation(Conversation conversation) {
this.conversationTracker.abandonConversation(conversation, new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller())); this.conversationTracker.abandonConversation(conversation, new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller()));
} }
public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
this.conversationTracker.abandonConversation(conversation, details); this.conversationTracker.abandonConversation(conversation, details);
} }
public void acceptConversationInput(String input) { public void acceptConversationInput(String input) {
this.conversationTracker.acceptConversationInput(input); this.conversationTracker.acceptConversationInput(input);
} }
public boolean isConversing() { public boolean isConversing() {
return this.conversationTracker.isConversing(); return this.conversationTracker.isConversing();
} }
public boolean isPermissionSet(String name) { public boolean isPermissionSet(String name) {
return this.perm.isPermissionSet(name); return this.perm.isPermissionSet(name);
} }
public boolean isPermissionSet(Permission perm) { public boolean isPermissionSet(Permission perm) {
return this.perm.isPermissionSet(perm); return this.perm.isPermissionSet(perm);
} }
public boolean hasPermission(String name) { public boolean hasPermission(String name) {
return this.perm.hasPermission(name); return this.perm.hasPermission(name);
} }
public boolean hasPermission(Permission perm) { public boolean hasPermission(Permission perm) {
return this.perm.hasPermission(perm); return this.perm.hasPermission(perm);
} }
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) {
return this.perm.addAttachment(plugin, name, value); return this.perm.addAttachment(plugin, name, value);
} }
public PermissionAttachment addAttachment(Plugin plugin) { public PermissionAttachment addAttachment(Plugin plugin) {
return this.perm.addAttachment(plugin); return this.perm.addAttachment(plugin);
} }
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) {
return this.perm.addAttachment(plugin, name, value, ticks); return this.perm.addAttachment(plugin, name, value, ticks);
} }
public PermissionAttachment addAttachment(Plugin plugin, int ticks) { public PermissionAttachment addAttachment(Plugin plugin, int ticks) {
return this.perm.addAttachment(plugin, ticks); return this.perm.addAttachment(plugin, ticks);
} }
public void removeAttachment(PermissionAttachment attachment) { public void removeAttachment(PermissionAttachment attachment) {
this.perm.removeAttachment(attachment); this.perm.removeAttachment(attachment);
} }
public void recalculatePermissions() { public void recalculatePermissions() {
this.perm.recalculatePermissions(); this.perm.recalculatePermissions();
} }
public Set<PermissionAttachmentInfo> getEffectivePermissions() { public Set<PermissionAttachmentInfo> getEffectivePermissions() {
return this.perm.getEffectivePermissions(); return this.perm.getEffectivePermissions();
} }
public boolean isPlayer() { public boolean isPlayer() {
return false; return false;
} }
public Server getServer() { public Server getServer() {
return Bukkit.getServer(); return Bukkit.getServer();
} }
} }

View File

@ -9,70 +9,70 @@ import java.util.LinkedList;
import java.util.logging.Level; import java.util.logging.Level;
public class SSHDConversationTracker { public class SSHDConversationTracker {
private LinkedList<Conversation> conversationQueue = new LinkedList<>(); private LinkedList<Conversation> conversationQueue = new LinkedList<>();
synchronized boolean beginConversation(Conversation conversation) { synchronized boolean beginConversation(Conversation conversation) {
if (!this.conversationQueue.contains(conversation)) { if (!this.conversationQueue.contains(conversation)) {
this.conversationQueue.addLast(conversation); this.conversationQueue.addLast(conversation);
if (this.conversationQueue.getFirst() == conversation) { if (this.conversationQueue.getFirst() == conversation) {
conversation.begin(); conversation.begin();
conversation.outputNextPrompt(); conversation.outputNextPrompt();
return true; return true;
} }
} }
return true; return true;
} }
synchronized void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { synchronized void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
if (!this.conversationQueue.isEmpty()) { if (!this.conversationQueue.isEmpty()) {
if (this.conversationQueue.getFirst() == conversation) { if (this.conversationQueue.getFirst() == conversation) {
conversation.abandon(details); conversation.abandon(details);
} }
if (this.conversationQueue.contains(conversation)) { if (this.conversationQueue.contains(conversation)) {
this.conversationQueue.remove(conversation); this.conversationQueue.remove(conversation);
} }
if (!this.conversationQueue.isEmpty()) { if (!this.conversationQueue.isEmpty()) {
this.conversationQueue.getFirst().outputNextPrompt(); this.conversationQueue.getFirst().outputNextPrompt();
} }
} }
} }
public synchronized void abandonAllConversations() { public synchronized void abandonAllConversations() {
LinkedList<Conversation> oldQueue = this.conversationQueue; LinkedList<Conversation> oldQueue = this.conversationQueue;
this.conversationQueue = new LinkedList<>(); this.conversationQueue = new LinkedList<>();
for (Conversation conversation : oldQueue) { for (Conversation conversation : oldQueue) {
try { try {
conversation.abandon(new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller())); conversation.abandon(new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller()));
} catch (Throwable var5) { } catch (Throwable var5) {
Bukkit.getLogger().log(Level.SEVERE, "Unexpected exception while abandoning a conversation", var5); Bukkit.getLogger().log(Level.SEVERE, "Unexpected exception while abandoning a conversation", var5);
} }
} }
} }
synchronized void acceptConversationInput(String input) { synchronized void acceptConversationInput(String input) {
if (this.isConversing()) { if (this.isConversing()) {
Conversation conversation = this.conversationQueue.getFirst(); Conversation conversation = this.conversationQueue.getFirst();
try { try {
conversation.acceptInput(input); conversation.acceptInput(input);
} catch (Throwable var4) { } catch (Throwable var4) {
conversation.getContext().getPlugin().getLogger().log(Level.WARNING, String.format("Plugin %s generated an exception whilst handling conversation input", conversation.getContext().getPlugin().getDescription().getFullName()), var4); conversation.getContext().getPlugin().getLogger().log(Level.WARNING, String.format("Plugin %s generated an exception whilst handling conversation input", conversation.getContext().getPlugin().getDescription().getFullName()), var4);
} }
} }
} }
synchronized boolean isConversing() { synchronized boolean isConversing() {
return !this.conversationQueue.isEmpty(); return !this.conversationQueue.isEmpty();
} }
public synchronized boolean isConversingModaly() { public synchronized boolean isConversingModaly() {
return this.isConversing() && this.conversationQueue.getFirst().isModal(); return this.isConversing() && this.conversationQueue.getFirst().isModal();
} }
} }

View File

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