9 Commits

Author SHA1 Message Date
Zachery
bf72021439 Update MkpasswdCommand.java 2019-10-16 00:45:54 -05:00
Justin Crawford
dd76551cf9 Increment project version for sponge release 2019-10-13 01:03:00 -07:00
Justin Crawford
a7918c68aa Finish the port to Sponge. Things seem to work well. 2019-10-13 01:00:39 -07:00
Justin Crawford
41b561a88c Well. It compiles. Can't say much.
Code overhaul for the sponge API which is vastly different from
bungeecord port. This still requires lots of work though.
2019-10-12 03:46:05 -07:00
Zachery
67aa5c63ea Begining of Sponge port for Minecraft-SSHD
The main class and MkpasswordCommand are (hopefully) working, the config
is getting there.
2019-10-11 21:10:24 -05:00
Zachery
7c6ae57513 Update Readme for Sponge 2019-10-11 20:19:19 -05:00
Justin Crawford
170a96eb94 Update mkpasswd to be slightly more secure
Try to ensure that the mkpasswd command run in ssh sessions only echos to
ssh client running that command. This gives us slightly more security
against other session users seeing the hashed password.

Fixed console sending with some of the APIs

Updated version to 1.3.7 to match for next spigot + bungeecord release.
2019-10-10 19:48:32 -07:00
Justin Crawford
ec1ed8cb9e Add new logo 2019-10-09 00:07:16 -07:00
Zachery
67146ce589 Change the load method to startup
This lets the plugin start before the world is generated, this is done so you can SSH into the server while it's still technically starting.
2019-10-08 17:37:54 -05:00
20 changed files with 1019 additions and 939 deletions

View File

@@ -1,5 +1,5 @@
--- ---
Language: 'Java' #BasedOnStyle: WebKit
TabWidth: '4' TabWidth: '4'
IndentWidth: '4' IndentWidth: '4'
UseTab: 'Always' UseTab: 'Always'
@@ -7,7 +7,6 @@ AlignOperands: 'true'
AlignAfterOpenBracket: 'Align' AlignAfterOpenBracket: 'Align'
AlignConsecutiveAssignments: 'true' AlignConsecutiveAssignments: 'true'
AlignConsecutiveDeclarations: 'true' AlignConsecutiveDeclarations: 'true'
AlignConsecutiveMacros: 'true'
AlignEscapedNewlines: 'Left' AlignEscapedNewlines: 'Left'
AlignTrailingComments: 'true' AlignTrailingComments: 'true'
AllowAllParametersOfDeclarationOnNextLine: 'true' AllowAllParametersOfDeclarationOnNextLine: 'true'
@@ -21,20 +20,35 @@ AlwaysBreakTemplateDeclarations: 'true'
AlwaysBreakBeforeMultilineStrings: 'false' AlwaysBreakBeforeMultilineStrings: 'false'
BinPackArguments: 'false' BinPackArguments: 'false'
BinPackParameters: 'false' BinPackParameters: 'false'
BreakBeforeBraces: 'Allman' BreakBeforeBraces: 'Custom'
BraceWrapping:
AfterEnum: 'true'
AfterClass: 'true'
AfterControlStatement: 'true'
AfterStruct: 'true'
AfterFunction: 'true'
AfterNamespace: 'true'
AfterUnion: 'true'
AfterExternBlock: 'true'
BeforeCatch: 'true'
BeforeElse: 'true'
SplitEmptyRecord: 'false'
SplitEmptyNamespace: 'false'
SplitEmptyFunction: 'false'
BreakBeforeBinaryOperators: 'true' BreakBeforeBinaryOperators: 'true'
BreakBeforeTernaryOperators: 'false' BreakBeforeTernaryOperators: 'false'
BreakConstructorInitializers: 'AfterColon' BreakConstructorInitializersBeforeComma: 'false'
BreakBeforeInheritanceComma: 'false' BreakBeforeInheritanceComma: 'false'
BreakAfterJavaFieldAnnotations: 'true'
BreakStringLiterals: 'true' BreakStringLiterals: 'true'
ColumnLimit: '140' ColumnLimit: '140'
CompactNamespaces: 'false' CompactNamespaces: 'false'
Cpp11BracedListStyle: 'true'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
DerivePointerAlignment: 'false' DerivePointerAlignment: 'false'
IndentCaseLabels: 'true' IndentCaseLabels: 'true'
IndentPPDirectives: 'AfterHash' IndentPPDirectives: 'AfterHash'
KeepEmptyLinesAtTheStartOfBlocks: 'true' KeepEmptyLinesAtTheStartOfBlocks: 'true'
Language: 'Java'
NamespaceIndentation: 'All' NamespaceIndentation: 'All'
PointerAlignment: 'Right' PointerAlignment: 'Right'
ReflowComments: 'true' ReflowComments: 'true'

View File

@@ -1,5 +1,5 @@
Minecraft-SSHD (BungeeCord Edition!) Minecraft-SSHD (Sponge Edition!)
================================= ===========
[![Build Status](https://travis-ci.org/Justasic/Minecraft-SSHD.svg?branch=master)](https://travis-ci.org/Justasic/Minecraft-SSHD) [![Build Status](https://travis-ci.org/Justasic/Minecraft-SSHD.svg?branch=master)](https://travis-ci.org/Justasic/Minecraft-SSHD)
[![Release](https://img.shields.io/github/release/Justasic/Minecraft-SSHD.svg?label=Release&maxAge=60)](https://github.com/Justasic/Minecraft-SSHD/releases/latest) [![Release](https://img.shields.io/github/release/Justasic/Minecraft-SSHD.svg?label=Release&maxAge=60)](https://github.com/Justasic/Minecraft-SSHD/releases/latest)
@@ -9,7 +9,7 @@ Minecraft-SSHD (BungeeCord Edition!)
**Have you ever wished you could remotely access your server's admin console without having to setup a complex remote access system? Now you can with Minecraft-SSHD!** **Have you ever wished you could remotely access your server's admin console without having to setup a complex remote access system? Now you can with Minecraft-SSHD!**
Minecraft-SSHD securely exposes your BungeeCord admin console and the server filesystem using the SSH protocol - the same protocol that serves as the secure foundation for nearly all remote server administration.<br/> Minecraft-SSHD securely exposes your Sponge admin console and the server filesystem using the SSH protocol - the same protocol that serves as the secure foundation for nearly all remote server administration.<br/>
- Compatible with all ssh clients, regardless of operating system. - Compatible with all ssh clients, regardless of operating system.
- Remotely view your server log in real-time. - Remotely view your server log in real-time.
@@ -17,12 +17,12 @@ Minecraft-SSHD securely exposes your BungeeCord admin console and the server fil
- Supports multiple concurrent remote connections. - Supports multiple concurrent remote connections.
- Strong identity support using public key authentication. - Strong identity support using public key authentication.
- Audit history who is running commands in the console - Audit history who is running commands in the console
- Run BungeeCord without using screen or tmux (by adding `-noconsole`) - Run Spigot without using screen or tmux (by adding `-noconsole`)
- Remotely script your server by issuing one-off console commands with ssh. - Remotely script your server by issuing one-off console commands with ssh.
### Why should I use Minecraft-SSHD? ### Why should I use Minecraft-SSHD?
- You are in a shared hosting environment that only gives you access to the - log files. - You are in a shared hosting environment that only gives you access to the log files.
- You want to share access to your server console, but don't want to give anybody access to the machine its running on. - You want to share access to your server console, but don't want to give anybody access to the machine its running on.
- You always wanted to use RCON, but want to see the server log as well. - You always wanted to use RCON, but want to see the server log as well.
- You are tired of running your server in a GNU screen or tmux session. - You are tired of running your server in a GNU screen or tmux session.
@@ -40,7 +40,7 @@ Screenshots
Setting Up Public Key Authentication Setting Up Public Key Authentication
==================================== ====================================
Setting up public key authentication with SSH requires first generating a public and private key pair and then installing just the public key on your BungeeCord server. This plugin supports all modern SSH key algoritms as OpenSSH. You can paste as many public keys from the methods below into each user's authorization file if they have multiple private keys. You can read [this guide from ssh.com](https://www.ssh.com/ssh/keygen/) if you want a better explanation on different key files. Setting up public key authentication with SSH requires first generating a public and private key pair and then installing just the public key on your Spigot server. This plugin supports all modern SSH key algoritms as OpenSSH. You can paste as many public keys from the methods below into each user's authorization file if they have multiple private keys. You can read [this guide from ssh.com](https://www.ssh.com/ssh/keygen/) if you want a better explanation on different key files.
## Generating New Keys ## Generating New Keys

89
Sponge-SSHD.iml Normal file
View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="minecraft" name="Minecraft">
<configuration>
<autoDetectTypes>
<platformType>SPONGE</platformType>
</autoDetectTypes>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: org.apache.sshd:sshd-core:2.3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.sshd:sshd-mina:2.3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.sshd:sshd-common:2.3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.sshd:sshd-sftp:2.3.0" level="project" />
<orderEntry type="library" name="Maven: net.i2p.crypto:eddsa:0.3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.mina:mina-core:2.1.3" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.28" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-jdk14:1.7.28" level="project" />
<orderEntry type="library" name="Maven: jline:jline:2.12.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.logging.log4j:log4j-core:2.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.logging.log4j:log4j-api:2.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-codec:commons-codec:1.10" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.spongepowered:spongeapi:7.1.0" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:guava:21.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.errorprone:error_prone_annotations:2.0.15" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.gson:gson:2.8.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.commons:commons-lang3:3.5" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:3.0.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.inject:guice:4.1.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: javax.inject:javax.inject:1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: aopalliance:aopalliance:1.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.github.ben-manes.caffeine:caffeine:2.5.4" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.github.ben-manes.caffeine:guava:2.5.4" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.spongepowered:plugin-meta:0.4.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.spongepowered:configurate-hocon:3.6" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.spongepowered:configurate-core:3.6" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.checkerframework:checker-qual:2.4.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.typesafe:config:1.3.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.spongepowered:configurate-gson:3.6" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.spongepowered:configurate-yaml:3.6" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.yaml:snakeyaml:1.18" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.flowpowered:flow-math:1.0.3" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.flowpowered:flow-noise:1.0.1-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.ow2.asm:asm:5.2" level="project" />
<orderEntry type="library" name="Maven: pl.project13.maven:git-commit-id-plugin:maven-plugin:3.0.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.maven:maven-plugin-api:3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.maven:maven-model:3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.maven:maven-artifact:3.0" level="project" />
<orderEntry type="library" name="Maven: org.sonatype.sisu:sisu-inject-plexus:1.4.2" level="project" />
<orderEntry type="library" name="Maven: org.sonatype.sisu:sisu-inject-bean:1.4.2" level="project" />
<orderEntry type="library" name="Maven: org.sonatype.sisu:sisu-guice:noaop:2.1.7" level="project" />
<orderEntry type="library" name="Maven: org.apache.maven:maven-core:3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.maven:maven-settings:3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.maven:maven-settings-builder:3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.maven:maven-repository-metadata:3.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.maven:maven-model-builder:3.0" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.apache.maven:maven-aether-provider:3.0" level="project" />
<orderEntry type="library" name="Maven: org.sonatype.aether:aether-impl:1.7" level="project" />
<orderEntry type="library" name="Maven: org.sonatype.aether:aether-spi:1.7" level="project" />
<orderEntry type="library" name="Maven: org.sonatype.aether:aether-api:1.7" level="project" />
<orderEntry type="library" name="Maven: org.sonatype.aether:aether-util:1.7" level="project" />
<orderEntry type="library" name="Maven: org.codehaus.plexus:plexus-interpolation:1.14" level="project" />
<orderEntry type="library" name="Maven: org.codehaus.plexus:plexus-utils:2.0.4" level="project" />
<orderEntry type="library" name="Maven: org.codehaus.plexus:plexus-classworlds:2.2.3" level="project" />
<orderEntry type="library" name="Maven: org.codehaus.plexus:plexus-component-annotations:1.5.5" level="project" />
<orderEntry type="library" name="Maven: org.sonatype.plexus:plexus-sec-dispatcher:1.3" level="project" />
<orderEntry type="library" name="Maven: org.sonatype.plexus:plexus-cipher:1.4" level="project" />
<orderEntry type="library" name="Maven: org.sonatype.plexus:plexus-build-api:0.0.7" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.9.9.3" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.9.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.9.9" level="project" />
<orderEntry type="library" name="Maven: joda-time:joda-time:2.10.3" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jgit:org.eclipse.jgit:5.2.2.201904231744-r" level="project" />
<orderEntry type="library" name="Maven: com.jcraft:jsch:0.1.54" level="project" />
<orderEntry type="library" name="Maven: com.jcraft:jzlib:1.1.1" level="project" />
<orderEntry type="library" name="Maven: com.googlecode.javaewah:JavaEWAH:1.1.6" level="project" />
</component>
</module>

208
pom.xml
View File

@@ -1,50 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?> <?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> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<artifactId>sshd-bungee</artifactId> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<version>2.0.0</version> <modelVersion>4.0.0</modelVersion>
<url>https://github.com/Justasic/Minecraft-SSHD/</url>
<properties> <groupId>com.ryanmichela</groupId>
<java.version>1.8</java.version> <artifactId>sshd</artifactId>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <version>2.0.0</version>
</properties> <packaging>jar</packaging>
<name>Sponge-SSHD</name>
<description>Sponge port for Minecraft-SSHD. SSH for your minecraft server!</description>
<url>https://github.com/Justasic/Minecraft-SSHD/</url>
<!-- Repositories --> <properties>
<repositories> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<repository> <maven.compiler.source>1.8</maven.compiler.source>
<id>spigot-repo</id> <maven.compiler.target>1.8</maven.compiler.target>
<url>https://hub.spigotmc.org/nexus/content/groups/public</url> </properties>
</repository>
</repositories>
<!-- License --> <repositories>
<licenses> <repository>
<license> <id>sponge</id>
<name>GPL2</name> <url>https://repo.spongepowered.org/maven</url>
<url>http://www.gnu.org/licenses/gpl-2.0.html</url> </repository>
</license> </repositories>
</licenses>
<!-- Dependencies -->
<dependencies>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.14-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependencies>
<dependency> <dependency>
<groupId>org.apache.sshd</groupId> <groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId> <artifactId>sshd-core</artifactId>
<version>2.3.0</version> <version>2.3.0</version>
<scope>compile</scope>
<type>jar</type>
</dependency> </dependency>
<dependency> <dependency>
@@ -57,8 +42,6 @@
<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>
@@ -73,24 +56,6 @@
<version>0.3.0</version> <version>0.3.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.28</version>
</dependency>
<dependency> <dependency>
<groupId>jline</groupId> <groupId>jline</groupId>
<artifactId>jline</artifactId> <artifactId>jline</artifactId>
@@ -100,7 +65,7 @@
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<version>2.0</version> <version>2.1</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -108,78 +73,65 @@
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
<version>2.1</version> <version>2.1</version>
</dependency> </dependency>
<!-- include Sponge API -->
<dependency>
<groupId>org.spongepowered</groupId>
<artifactId>spongeapi</artifactId>
<version>7.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<dependency> <build>
<groupId>commons-codec</groupId> <!-- Set our default goal to be clean and build a package -->
<artifactId>commons-codec</artifactId> <defaultGoal>clean package</defaultGoal>
<version>1.10</version> <!-- Include our git hash in the final name -->
<scope>provided</scope> <finalName>${project.artifactId}-${project.version}</finalName>
</dependency>
<dependency> <resources>
<groupId>commons-lang</groupId> <resource>
<artifactId>commons-lang</artifactId> <targetPath>.</targetPath>
<version>2.6</version> <filtering>true</filtering>
</dependency> <directory>${basedir}/src/main/resources</directory>
<includes>
<include>motd.txt</include>
</includes>
</resource>
</resources>
<dependency> <!-- Plugins -->
<groupId>org.projectlombok</groupId> <plugins>
<artifactId>lombok</artifactId> <plugin>
<version>1.18.4</version> <artifactId>maven-assembly-plugin</artifactId>
</dependency> <version>3.1.1</version>
</dependencies> <executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<finalName>${project.artifactId}-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- Build --> <!-- Compile plugin -->
<build> <plugin>
<defaultGoal>clean package</defaultGoal> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<resources> <version>3.7.0</version>
<resource> <configuration>
<targetPath>.</targetPath> <source>1.8</source>
<filtering>true</filtering> <target>1.8</target>
<directory>${basedir}/src/main/resources</directory> <showDeprecation>true</showDeprecation>
<includes> </configuration>
<include>plugin.yml</include> </plugin>
<include>config.yml</include> </plugins>
<include>motd.txt</include> </build>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<finalName>${project.name}-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- Compile plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
</project> </project>

View File

@@ -0,0 +1,222 @@
package com.ryanmichela.sshd;
// The below code was taken from md_5's BungeeCord project - Justin
/*
Copyright (c) 2012, md_5. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
The name of the author may not be used to endorse or promote products derived
from this software without specific prior written permission.
You may not use the software for commercial software hosting services without
written permission from the author.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Simplistic enumeration of all supported color values for chat.
*/
public enum ChatColor
{
/**
* Represents black.
*/
BLACK( '0', "black" ),
/**
* Represents dark blue.
*/
DARK_BLUE( '1', "dark_blue" ),
/**
* Represents dark green.
*/
DARK_GREEN( '2', "dark_green" ),
/**
* Represents dark blue (aqua).
*/
DARK_AQUA( '3', "dark_aqua" ),
/**
* Represents dark red.
*/
DARK_RED( '4', "dark_red" ),
/**
* Represents dark purple.
*/
DARK_PURPLE( '5', "dark_purple" ),
/**
* Represents gold.
*/
GOLD( '6', "gold" ),
/**
* Represents gray.
*/
GRAY( '7', "gray" ),
/**
* Represents dark gray.
*/
DARK_GRAY( '8', "dark_gray" ),
/**
* Represents blue.
*/
BLUE( '9', "blue" ),
/**
* Represents green.
*/
GREEN( 'a', "green" ),
/**
* Represents aqua.
*/
AQUA( 'b', "aqua" ),
/**
* Represents red.
*/
RED( 'c', "red" ),
/**
* Represents light purple.
*/
LIGHT_PURPLE( 'd', "light_purple" ),
/**
* Represents yellow.
*/
YELLOW( 'e', "yellow" ),
/**
* Represents white.
*/
WHITE( 'f', "white" ),
/**
* Represents magical characters that change around randomly.
*/
MAGIC( 'k', "obfuscated" ),
/**
* Makes the text bold.
*/
BOLD( 'l', "bold" ),
/**
* Makes a line appear through the text.
*/
STRIKETHROUGH( 'm', "strikethrough" ),
/**
* Makes the text appear underlined.
*/
UNDERLINE( 'n', "underline" ),
/**
* Makes the text italic.
*/
ITALIC( 'o', "italic" ),
/**
* Resets all previous chat colors or formats.
*/
RESET( 'r', "reset" );
/**
* The special character which prefixes all chat colour codes. Use this if
* you need to dynamically convert colour codes from your custom format.
*/
public static final char COLOR_CHAR = '\u00A7';
public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRr";
/**
* Pattern to remove all colour codes.
*/
public static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-OR]" );
/**
* Colour instances keyed by their active character.
*/
private static final Map<Character, ChatColor> BY_CHAR = new HashMap<Character, ChatColor>();
/**
* The code appended to {@link #COLOR_CHAR} to make usable colour.
*/
private final char code;
/**
* This colour's colour char prefixed by the {@link #COLOR_CHAR}.
*/
private final String toString;
private final String name;
public String getName() { return this.name; }
static
{
for ( ChatColor colour : values() )
{
BY_CHAR.put( colour.code, colour );
}
}
private ChatColor(char code, String name)
{
this.code = code;
this.name = name;
this.toString = new String( new char[]
{
COLOR_CHAR, code
} );
}
@Override
public String toString()
{
return toString;
}
/**
* Strips the given message of all color codes
*
* @param input String to strip of color
* @return A copy of the input string, without any coloring
*/
public static String stripColor(final String input)
{
if ( input == null )
{
return null;
}
return STRIP_COLOR_PATTERN.matcher( input ).replaceAll( "" );
}
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
{
char[] b = textToTranslate.toCharArray();
for ( int i = 0; i < b.length - 1; i++ )
{
if ( b[i] == altColorChar && ALL_CODES.indexOf( b[i + 1] ) > -1 )
{
b[i] = ChatColor.COLOR_CHAR;
b[i + 1] = Character.toLowerCase( b[i + 1] );
}
}
return new String( b );
}
/**
* Get the colour represented by the specified code.
*
* @param code the code to search for
* @return the mapped colour, or null if non exists
*/
public static ChatColor getByChar(char code)
{
return BY_CHAR.get( code );
}
}

View File

@@ -19,11 +19,11 @@ public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
public boolean authenticate(String username, String password, ServerSession ss) public boolean authenticate(String username, String password, ServerSession ss)
{ {
// Depending on our hash type, we have to try and figure out what we're doing. // Depending on our hash type, we have to try and figure out what we're doing.
String HashType = SshdPlugin.instance.configuration.getString("PasswordType"); String HashType = SshdPlugin.GetInstance().PasswordType;
String ConfigHash = SshdPlugin.instance.configuration.getString("Credentials." + username.trim()); String ConfigHash = SshdPlugin.GetInstance().config.configNode.getNode("Credentials", username.trim(), "password").getString();
if (ConfigHash == null) if (ConfigHash == null)
SshdPlugin.instance.getLogger().warning("Config has no such user: " + username); SshdPlugin.GetInstance().logger.warn("Config has no such user: " + username);
else else
{ {
try try
@@ -69,8 +69,8 @@ public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
} }
} }
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using " + HashType + "-based password authentication."); SshdPlugin.GetInstance().logger.info("Failed login for " + username + " using " + HashType + "-based password authentication.");
Integer tries = SshdPlugin.instance.configuration.getInt("LoginRetries"); Integer tries = SshdPlugin.GetInstance().LoginRetries;
try try
{ {
@@ -83,7 +83,7 @@ public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
if (this.FailCounts.get(username) >= tries) if (this.FailCounts.get(username) >= tries)
{ {
this.FailCounts.put(username, 0); this.FailCounts.put(username, 0);
SshdPlugin.instance.getLogger().info("Too many failures for " + username + ", disconnecting."); SshdPlugin.GetInstance().logger.info("Too many failures for " + username + ", disconnecting.");
ss.close(true); ss.close(true);
} }
} }

View File

@@ -3,18 +3,26 @@ package com.ryanmichela.sshd;
/** /**
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
import net.md_5.bungee.api.plugin.Command;
import jline.console.completer.Completer; import jline.console.completer.Completer;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.scheduler.SpongeExecutorService;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.logging.Level; import java.util.logging.Level;
public class ConsoleCommandCompleter implements Completer public class ConsoleCommandCompleter implements Completer
{ {
private SpongeExecutorService MinecraftExecutor;
public ConsoleCommandCompleter()
{
super();
this.MinecraftExecutor = Sponge.getScheduler().createSyncExecutor(SshdPlugin.GetInstance());
}
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) public int complete(final String buffer, final int cursor, final List<CharSequence> candidates)
{ {
Waitable<List<String>> waitable = new Waitable<List<String>>() Waitable<List<String>> waitable = new Waitable<List<String>>()
@@ -22,38 +30,33 @@ public class ConsoleCommandCompleter implements Completer
@Override @Override
protected List<String> evaluate() protected List<String> evaluate()
{ {
List<String> tabcomplete = new ArrayList<String>(); return Sponge.getCommandManager().getSuggestions(Sponge.getServer().getConsole(), buffer, null);
for (Map.Entry<String, Command> map : SshdPlugin.instance.getProxy().getPluginManager().getCommands())
{
String cmd = map.getKey();
Command value = map.getValue();
if (cmd.startsWith(buffer))
tabcomplete.add(cmd);
}
return tabcomplete;
} }
}; };
SshdPlugin.instance.getProxy().getScheduler().runAsync(SshdPlugin.instance, waitable); this.MinecraftExecutor.execute(waitable);
try try
{ {
List<String> offers = waitable.get(); List<String> offers = waitable.get();
if (offers == null) if (offers == null)
{
return cursor; return cursor;
}
candidates.addAll(offers); candidates.addAll(offers);
final int lastSpace = buffer.lastIndexOf(' '); final int lastSpace = buffer.lastIndexOf(' ');
if (lastSpace == -1) if (lastSpace == -1)
{
return cursor - buffer.length(); return cursor - buffer.length();
}
else else
{
return cursor - (buffer.length() - lastSpace - 1); return cursor - (buffer.length() - lastSpace - 1);
}
} }
catch (ExecutionException e) catch (ExecutionException e)
{ {
SshdPlugin.instance.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); SshdPlugin.GetInstance().logger.warn("Unhandled exception when tab completing", e);
} }
catch (InterruptedException e) catch (InterruptedException e)
{ {

View File

@@ -5,10 +5,7 @@ import org.apache.sshd.server.command.CommandFactory;
import org.apache.sshd.server.channel.ChannelSession; import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.Environment; import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback; import org.apache.sshd.server.ExitCallback;
import org.spongepowered.api.Sponge;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -58,15 +55,14 @@ public class ConsoleCommandFactory implements CommandFactory {
{ {
try try
{ {
SshdPlugin.instance.getLogger() SshdPlugin.GetInstance().logger
.info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command); .info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command);
if (!SshdPlugin.instance.getProxy().getPluginManager().dispatchCommand(SshdPlugin.instance.getProxy().getConsole(), command)) Sponge.getCommandManager().process(Sponge.getServer().getConsole(), command);
SshdPlugin.instance.getProxy().getConsole().sendMessage(new ComponentBuilder("Command not found").color(ChatColor.RED).create());
} }
catch (Exception e) catch (Exception e)
{ {
SshdPlugin.instance.getLogger().severe("Error processing command from SSH -" + e.getMessage()); SshdPlugin.GetInstance().logger.error("Error processing command from SSH -" + e.getMessage());
} }
finally finally
{ {

View File

@@ -4,26 +4,17 @@ package com.ryanmichela.sshd;
* Copyright 2013 Ryan Michela * Copyright 2013 Ryan Michela
*/ */
import net.md_5.bungee.api.ChatColor; import com.ryanmichela.sshd.ChatColor;
import org.fusesource.jansi.Ansi; import org.fusesource.jansi.Ansi;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
public class ConsoleLogFormatter extends Formatter public class ConsoleLogFormatter
{ {
private SimpleDateFormat dateFormat; private static final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
private static final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
public ConsoleLogFormatter()
{
this.dateFormat = new SimpleDateFormat("HH:mm:ss");
}
public static String ColorizeString(String str) public static String ColorizeString(String str)
{ {
@@ -67,43 +58,5 @@ public class ConsoleLogFormatter extends Formatter
result += Ansi.ansi().reset().toString(); result += Ansi.ansi().reset().toString();
return result; return result;
} }
public String format(LogRecord logrecord)
{
try
{
Class.forName("org.bukkit.craftbukkit.command.ColouredConsoleSender");
}
catch (ClassNotFoundException ignored)
{
// MEANS WE'RE ON PAPER/TACO/OTHER SHIT
colorize(logrecord);
}
StringBuilder stringbuilder = new StringBuilder();
stringbuilder.append(" [");
stringbuilder.append(this.dateFormat.format(logrecord.getMillis())).append(" ");
stringbuilder.append(logrecord.getLevel().getName()).append("]: ");
stringbuilder.append(this.formatMessage(logrecord));
stringbuilder.append('\n');
Throwable throwable = logrecord.getThrown();
if (throwable != null)
{
StringWriter stringwriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringwriter));
stringbuilder.append(stringwriter.toString());
}
return stringbuilder.toString().replace("\n", "\r\n");
}
private void colorize(LogRecord logrecord)
{
String result = ColorizeString(logrecord.getMessage());
logrecord.setMessage(result);
}
} }

View File

@@ -3,15 +3,12 @@ package com.ryanmichela.sshd;
import com.ryanmichela.sshd.ConsoleCommandCompleter; import com.ryanmichela.sshd.ConsoleCommandCompleter;
import com.ryanmichela.sshd.ConsoleLogFormatter; import com.ryanmichela.sshd.ConsoleLogFormatter;
import com.ryanmichela.sshd.FlushyOutputStream; import com.ryanmichela.sshd.FlushyOutputStream;
import com.ryanmichela.sshd.FlushyStreamHandler;
import com.ryanmichela.sshd.SshTerminal; import com.ryanmichela.sshd.SshTerminal;
import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.SshdPlugin;
import com.ryanmichela.sshd.StreamHandlerAppender;
import com.ryanmichela.sshd.implementations.SSHDCommandSender; import com.ryanmichela.sshd.implementations.SSHDCommandSender;
import com.ryanmichela.sshd.ConsoleLogFormatter; import com.ryanmichela.sshd.ConsoleLogFormatter;
import jline.console.ConsoleReader; import jline.console.ConsoleReader;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.Logger;
import org.apache.sshd.common.Factory; import org.apache.sshd.common.Factory;
@@ -20,6 +17,12 @@ import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.channel.ChannelSession; import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.Environment; import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback; import org.apache.sshd.server.ExitCallback;
import org.spongepowered.api.MinecraftVersion;
import org.spongepowered.api.Platform;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandManager;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.scheduler.SpongeExecutorService;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -33,8 +36,6 @@ import java.util.StringTokenizer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.StreamHandler; import java.util.logging.StreamHandler;
import static com.ryanmichela.sshd.SshdPlugin.instance;
public class ConsoleShellFactory implements ShellFactory { public class ConsoleShellFactory implements ShellFactory {
public Command createShell(ChannelSession cs) { public Command createShell(ChannelSession cs) {
@@ -51,7 +52,7 @@ public class ConsoleShellFactory implements ShellFactory {
private Thread thread; private Thread thread;
private String Username; private String Username;
StreamHandler streamHandler; StreamHandlerAppender streamHandlerAppender;
public ConsoleReader ConsoleReader; public ConsoleReader ConsoleReader;
public SSHDCommandSender SshdCommandSender; public SSHDCommandSender SshdCommandSender;
@@ -96,9 +97,9 @@ public class ConsoleShellFactory implements ShellFactory {
this.ConsoleReader.setExpandEvents(true); this.ConsoleReader.setExpandEvents(true);
this.ConsoleReader.addCompleter(new ConsoleCommandCompleter()); this.ConsoleReader.addCompleter(new ConsoleCommandCompleter());
streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), this.ConsoleReader); this.streamHandlerAppender = new StreamHandlerAppender(this.ConsoleReader);
SshdPlugin.instance.getProxy().getLogger().addHandler(this.streamHandler); ((Logger)LogManager.getRootLogger()).addAppender(this.streamHandlerAppender);
this.environment = env; this.environment = env;
this.Username = env.getEnv().get(Environment.ENV_USER); this.Username = env.getEnv().get(Environment.ENV_USER);
@@ -109,19 +110,22 @@ public class ConsoleShellFactory implements ShellFactory {
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace();
throw new IOException("Error starting shell", e); throw new IOException("Error starting shell", e);
} }
} }
@Override @Override
public void destroy(ChannelSession cs) { SshdPlugin.instance.getProxy().getLogger().removeHandler(this.streamHandler); } public void destroy(ChannelSession cs) { ((Logger)LogManager.getRootLogger()).removeAppender(this.streamHandlerAppender); }
public void run() public void run()
{ {
try try
{ {
if (!instance.configuration.getString("Mode").equals("RPC")) // Get the sponge command manager so we can execute commands.
CommandManager CmdManager = Sponge.getCommandManager();
SpongeExecutorService MinecraftExecutor = Sponge.getScheduler().createSyncExecutor(SshdPlugin.GetInstance());
// Print the SSHD motd.
if (!SshdPlugin.GetInstance().Mode.equals("RPC"))
printPreamble(this.ConsoleReader); printPreamble(this.ConsoleReader);
while (true) while (true)
{ {
@@ -139,47 +143,53 @@ public class ConsoleShellFactory implements ShellFactory {
if (command.equals("cls")) if (command.equals("cls"))
{ {
this.ConsoleReader.clearScreen(); this.ConsoleReader.clearScreen();
this.ConsoleReader.drawLine();
this.ConsoleReader.flush();
continue; continue;
} }
// Hide the mkpasswd command input from other users. // Hide the mkpasswd command input from other users.
Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd"); Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd");
instance.getProxy().getScheduler().runAsync( MinecraftExecutor.submit(() ->
instance, () ->
{ {
if (instance.configuration.getString("Mode").equals("RPC") && command.startsWith("rpc")) if (SshdPlugin.GetInstance().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());
if (!instance.getProxy().getPluginManager().dispatchCommand(this.SshdCommandSender, cmd)) CmdManager.process(this.SshdCommandSender, cmd);
instance.getProxy().getConsole().sendMessage(new ComponentBuilder("Command not found").color(ChatColor.RED).create());
} }
else else
{ {
// Don't send our mkpasswd command output. This will echo passwords back
// to the console for all to see. This command is strictly between
// our plugin and the connected client.
if (!mkpasswd) if (!mkpasswd)
instance.getLogger().info("<" + this.Username + "> " + command); {
SshdPlugin.GetInstance().logger.info("<" + this.Username + "> " + command);
if (!instance.getProxy().getPluginManager().dispatchCommand(this.SshdCommandSender, command)) CmdManager.process(Sponge.getServer().getConsole(), command);
instance.getProxy().getConsole().sendMessage(new ComponentBuilder("Command not found").color(ChatColor.RED).create()); }
else
{
CmdManager.process(this.SshdCommandSender, command);
}
} }
}); });
} }
} }
catch (IOException e) catch (IOException e)
{ {
e.printStackTrace(); SshdPlugin.GetInstance().logger.error("Error processing command from SSH", e);
instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
} }
finally finally
{ {
instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH."); SshdPlugin.GetInstance().logger.info(this.Username + " disconnected from SSH.");
callback.onExit(0); callback.onExit(0);
} }
} }
private void printPreamble(ConsoleReader cr) throws IOException private void printPreamble(ConsoleReader cr) throws IOException
{ {
File f = new File(instance.getDataFolder(), "motd.txt"); File f = new File(SshdPlugin.GetInstance().ConfigDir.toFile(), "motd.txt");
try try
{ {
BufferedReader br = new BufferedReader(new FileReader(f)); BufferedReader br = new BufferedReader(new FileReader(f));
@@ -190,18 +200,26 @@ public class ConsoleShellFactory implements ShellFactory {
} }
catch (FileNotFoundException e) catch (FileNotFoundException e)
{ {
instance.getLogger().log(Level.WARNING, "Could not open " + f + ": File does not exist."); SshdPlugin.GetInstance().logger.warn("Could not open " + f + ": File does not exist.");
// Not showing the SSH motd is not a fatal failure, let the session continue. // Not showing the SSH motd is not a fatal failure, let the session continue.
} }
// Doesn't really guarantee our actual system hostname but // Doesn't really guarantee our actual system hostname but
// it's better than not having one at all. // it's better than not having one at all.
cr.println("Connected to: " + InetAddress.getLocalHost().getHostName() + " (BungeeCord)\r"); Platform p = Sponge.getPlatform();
// Since BungeeCord is stupid, we have to parse the config file and the the MOTD from it that way... MinecraftVersion mv = p.getMinecraftVersion();
// If you try to use the method getMotd() it returns that it can't be referenced from a non-static context, which is stupid. PluginContainer pc = p.getContainer(Platform.Component.API);
cr.println(ConsoleLogFormatter.ColorizeString(instance.configuration.getString("motd")).replaceAll("\n", "\r\n")); String str = String.format(
"Connected to: %s -- Minecraft %s (%s %s)",
InetAddress.getLocalHost().getHostName(),
mv.getName(),
pc.getName(),
pc.getVersion().orElse("<Unknown>"));
cr.println(str + "\r");
cr.println(ConsoleLogFormatter.ColorizeString(Sponge.getServer().getMotd().toPlain()).replaceAll("\n", "\r\n"));
cr.println("\r"); cr.println("\r");
cr.println("Type 'exit' or press Ctrl+D exit the shell." + "\r"); cr.println("Type 'exit' or CTRL+D to exit the shell." + "\r");
cr.println("===============================================" + "\r"); cr.println("===============================================" + "\r");
} }
} }

View File

@@ -1,61 +0,0 @@
package com.ryanmichela.sshd;
import jline.console.ConsoleReader;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.exception.SshChannelClosedException;
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 (SshChannelClosedException ex)
{
// do nothing
}
catch (IOException ex)
{
Logger.getLogger(FlushyStreamHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
}

View File

@@ -1,85 +1,121 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
/*
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.Command;
import org.bukkit.entity.Player;
*/
import java.util.Arrays; import java.util.Arrays;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import com.ryanmichela.sshd.Cryptography; import com.ryanmichela.sshd.Cryptography;
import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.SshdPlugin;
public class MkpasswdCommand extends Command import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandException;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.args.GenericArguments;
import org.spongepowered.api.command.spec.CommandExecutor;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.event.command.TabCompleteEvent;
import org.spongepowered.api.text.Text;
public class MkpasswdCommand implements CommandExecutor
{ {
public MkpasswdCommand() private static CommandSpec cmdspec;
public static void BuildCommand()
{ {
super("mkpasswd"); CommandSpec pbkdf2 = CommandSpec.builder()
} .description(Text.of("PBKDF2 hashed password"))
.permission("sshd.mkpasswd.pbkdf2")
public void SendSyntax(CommandSender sender, boolean invalid) .arguments(GenericArguments.remainingJoinedStrings(Text.of("password")))
{ .executor((CommandSource source, CommandContext args) -> {
if (invalid) try
sender.sendMessage(new ComponentBuilder("Invalid Syntax").color(ChatColor.RED).create()); {
sender.sendMessage(new ComponentBuilder("/mkpasswd <help|hash> <password>").color(ChatColor.GREEN).create()); source.sendMessage(Text.of("\u00A79Your Hash: " + Cryptography.PBKDF2_HashPassword(args.<String>getOne("password").get())));
sender.sendMessage(new ComponentBuilder("Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN").color(ChatColor.BLUE).create()); }
catch (Exception e)
{
e.printStackTrace();
return null;
}
return CommandResult.success();
})
.build();
CommandSpec bcrypt = CommandSpec.builder()
.description(Text.of("BCrypt hashed password"))
.permission("sshd.mkpasswd.bcrypt")
.arguments(GenericArguments.remainingJoinedStrings(Text.of("password")))
.executor((CommandSource source, CommandContext args) -> {
try
{
source.sendMessage(Text.of("\u00A79Your Hash: " + Cryptography.BCrypt_HashPassword(args.<String>getOne("password").get())));
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
return CommandResult.success();
})
.build();
CommandSpec sha256 = CommandSpec.builder()
.description(Text.of("SHA256 hashed password"))
.permission("sshd.mkpasswd.sha256")
.arguments(GenericArguments.remainingJoinedStrings(Text.of("password")))
.executor((CommandSource source, CommandContext args) -> {
try
{
source.sendMessage(Text.of("\u00A79Your Hash: " + Cryptography.SHA256_HashPassword(args.<String>getOne("password").get())));
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
return CommandResult.success();
})
.build();
// The plain text "encryption" method
CommandSpec plain = CommandSpec.builder()
.description(Text.of("Plain text password (insecure)"))
.permission("sshd.mkpasswd.plain")
.arguments(GenericArguments.remainingJoinedStrings(Text.of("password")))
.executor((CommandSource source, CommandContext args) -> {
source.sendMessage(Text.of("\u00A79Your Hash: \u00A7cIt's literally your unhashed password."));
return CommandResult.success();
})
.build();
// the root "mkpasswd" command
cmdspec = CommandSpec.builder()
.description(Text.of("Create an SSHd password using hashes"))
.extendedDescription(Text.of("Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN"))
.permission("sshd.mkpasswd")
.child(plain, "plain")
.child(sha256, "sha256")
.child(bcrypt, "bcrypt")
.child(pbkdf2, "pbkdf2")
.executor(new MkpasswdCommand())
.build();
// Register our command with Sponge.
Sponge.getCommandManager().register(SshdPlugin.GetInstance(), cmdspec, "mkpasswd");
} }
// so sponge needed this, still figuring out the sponge API ~ Zach
@Override @Override
public void execute(CommandSender sender, String[] args) public CommandResult execute(CommandSource src, CommandContext args) throws CommandException
{ {
String algoritm, password; // This command doesn't do anything.
try src.sendMessage(Text.of("\u00A7a/mkpasswd <hash> <password...>\u00A7r"));
{ src.sendMessage(Text.of("\u00A79Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN\u00A7r"));
// Stupid bukkit, we have to concatenate the arguments together if they're using return CommandResult.success();
// spaces in their passwords otherwise it won't be as strong as it should be.
algoritm = args[0];
password = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
if (password.length() == 0)
throw new ArrayIndexOutOfBoundsException(); // shortcut
}
catch (ArrayIndexOutOfBoundsException e)
{
this.SendSyntax(sender, false);
return;
}
// If they're a player, check and make sure they have a permission
// If they're not a player (aka, the console), just return true.
boolean hasperm = (sender instanceof ProxiedPlayer) ? ((ProxiedPlayer)sender).hasPermission("sshd.mkpasswd") : true;
if (hasperm)
{
try
{
String hash = "";
// Dumb but whatever. Some people are really dense.
if (algoritm.equalsIgnoreCase("PLAIN"))
{
// I mean c'mon...
sender.sendMessage("\u00A79Your Hash: \u00A7cIt's literally your unhashed password.");
return;
}
else if (algoritm.equalsIgnoreCase("pbkdf2"))
hash = Cryptography.PBKDF2_HashPassword(password);
else if (algoritm.equalsIgnoreCase("bcrypt"))
hash = Cryptography.BCrypt_HashPassword(password);
else if (algoritm.equalsIgnoreCase("sha256"))
hash = Cryptography.SHA256_HashPassword(password);
else
{
this.SendSyntax(sender, !algoritm.equalsIgnoreCase("help"));
return;
}
sender.sendMessage(new ComponentBuilder("Your Hash: " + hash).color(ChatColor.BLUE).create());
}
catch (Exception e)
{
// We're console, just print the stack trace.
e.printStackTrace();
}
}
} }
} }

View File

@@ -1,7 +1,5 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
import net.md_5.bungee.config.Configuration;
import org.apache.commons.lang.ArrayUtils;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator; import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
@@ -21,13 +19,14 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator
{ {
private File authorizedKeysDir; private File authorizedKeysDir;
private Map<String, Integer> FailCounts = new HashMap<String, Integer>(); private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
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.configuration.getInt("LoginRetries"); Integer tries = SshdPlugin.GetInstance().LoginRetries;
if (keyFile.exists()) if (keyFile.exists())
{ {
@@ -47,7 +46,7 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator
} }
else else
{ {
SshdPlugin.instance.getLogger().info( SshdPlugin.GetInstance().logger.info(
username + " failed authentication via SSH session using key file " + keyFile.getAbsolutePath()); username + " failed authentication via SSH session using key file " + keyFile.getAbsolutePath());
} }
@@ -60,7 +59,7 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator
if (this.FailCounts.get(username) >= tries) if (this.FailCounts.get(username) >= tries)
{ {
this.FailCounts.put(username, 0); this.FailCounts.put(username, 0);
SshdPlugin.instance.getLogger().info("Too many failures for " + username + ", disconnecting."); SshdPlugin.GetInstance().logger.info("Too many failures for " + username + ", disconnecting.");
session.close(true); session.close(true);
} }
@@ -68,12 +67,12 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator
} }
catch (Exception e) catch (Exception e)
{ {
SshdPlugin.instance.getLogger().severe("Failed to process public key " + keyFile.getAbsolutePath() + " " + e.getMessage()); SshdPlugin.GetInstance().logger.error("Failed to process public key " + keyFile.getAbsolutePath() + " " + e.getMessage());
} }
} }
else else
{ {
SshdPlugin.instance.getLogger().warning("Could not locate public key for " + username SshdPlugin.GetInstance().logger.error("Could not locate public key for " + username
+ ". Make sure the user's key is named the same as their user name " + ". Make sure the user's key is named the same as their user name "
+ "without a file extension."); + "without a file extension.");
} }

View File

@@ -1,15 +1,18 @@
package com.ryanmichela.sshd; package com.ryanmichela.sshd;
import net.md_5.bungee.api.ProxyServer; import com.ryanmichela.sshd.utils.Config;
import net.md_5.bungee.config.Configuration; import org.spongepowered.api.Sponge;
import net.md_5.bungee.config.ConfigurationProvider; import org.spongepowered.api.command.spec.CommandSpec;
import net.md_5.bungee.config.YamlConfiguration; import org.spongepowered.api.config.DefaultConfig;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory; import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
import org.apache.sshd.server.SshServer; import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory; import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.config.*;
import com.ryanmichela.sshd.ConsoleShellFactory; import com.ryanmichela.sshd.ConsoleShellFactory;
import com.ryanmichela.sshd.MkpasswdCommand; import com.ryanmichela.sshd.MkpasswdCommand;
@@ -19,30 +22,71 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections; import java.util.Collections;
import java.util.logging.Level; import java.util.logging.Level;
/** import com.google.inject.Inject;
* Copyright 2013 Ryan Michela import org.slf4j.Logger;
*/ import org.spongepowered.api.text.Text;
public final class SshdPlugin extends Plugin
@Plugin(
id = "sshd",
name = "Minecraft-SSHD",
version = "1.3.7",
description = "Sponge port for Minecraft-SSHD. SSH for your minecraft server!",
authors={"Justin Crawford", "Zachery Coleman"}
)
public class SshdPlugin
{ {
private SshServer sshd; String ListenAddress = "";
public static SshdPlugin instance; Integer Port = 1025;
private File file; String Mode = "";
public Configuration configuration; Boolean EnableSFTP = true;
Integer LoginRetries = 3;
String PasswordType = "";
// Credentials
@Override public void onLoad() private SshServer sshd;
private static SshdPlugin instance;
@Inject
public Logger logger;
@Inject
@DefaultConfig(sharedRoot = false)
public Path DefaultConfig;
@Inject
@ConfigDir(sharedRoot = false)
public Path ConfigDir;
public Config config;
@Listener
public void onServerStart(GameStartedServerEvent event)
{ {
file = new File(getDataFolder(), "config.yml"); instance = this;
// Parse our config
config = new Config();
config.setup();
File authorizedKeys = new File(getDataFolder(), "authorized_keys"); // Make sure our authorized_keys folder exists
File authorizedKeys = new File(this.ConfigDir.toFile(), "authorized_keys");
if (!authorizedKeys.exists()) if (!authorizedKeys.exists())
authorizedKeys.mkdirs(); authorizedKeys.mkdirs();
// Now include it in our dealio here
this.Mode = config.configNode.getNode("Mode").getString();
this.PasswordType = config.configNode.getNode("PasswordType").getString();
this.ListenAddress = config.configNode.getNode("ListenAddress").getString();
this.Port = config.configNode.getNode("Port").getInt();
this.LoginRetries = config.configNode.getNode("LoginRetries").getInt();
this.EnableSFTP = config.configNode.getNode("EnableSFTP").getBoolean();
try try
{ {
File motd = new File(getDataFolder(), "motd.txt"); File motd = new File(this.ConfigDir.toFile(), "motd.txt");
if (!motd.exists()) if (!motd.exists())
{ {
InputStream link = (getClass().getResourceAsStream("/motd.txt")); InputStream link = (getClass().getResourceAsStream("/motd.txt"));
@@ -54,52 +98,25 @@ public final class SshdPlugin extends Plugin
e.printStackTrace(); e.printStackTrace();
} }
try
{
if (!file.exists())
{
// Copy our config file.
InputStream link = (getClass().getResourceAsStream("/config.yml"));
Files.copy(link, file.getAbsoluteFile().toPath());
}
configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(file);
}
catch (IOException e)
{
e.printStackTrace();
}
// 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 = SshServer.setUpDefaultServer();
sshd.setPort(configuration.getInt("Port", 1025)); sshd.setPort(this.Port);
String host = configuration.getString("ListenAddress", "all"); sshd.setHost(this.ListenAddress.equals("all") ? null : this.ListenAddress);
sshd.setHost(host.equals("all") ? null : host);
File hostKey = new File(getDataFolder(), "hostkey"); File hostKey = new File(this.ConfigDir.toFile(), "hostkey");
File authorizedKeys = new File(getDataFolder(), "authorized_keys");
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey.toPath())); sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey.toPath()));
sshd.setShellFactory(new ConsoleShellFactory()); sshd.setShellFactory(new ConsoleShellFactory());
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator()); sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys)); sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
if (configuration.getBoolean("EnableSFTP")) if (this.EnableSFTP)
{ {
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory())); sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
sshd.setFileSystemFactory( sshd.setFileSystemFactory(new VirtualFileSystemFactory(this.ConfigDir.getParent().getParent()));
new VirtualFileSystemFactory(FileSystems.getDefault().getPath(getDataFolder().getAbsolutePath()).getParent().getParent()));
} }
getProxy().getPluginManager().registerCommand(this, new MkpasswdCommand()); MkpasswdCommand.BuildCommand();
sshd.setCommandFactory(new ConsoleCommandFactory()); sshd.setCommandFactory(new ConsoleCommandFactory());
try try
@@ -108,27 +125,19 @@ public final class SshdPlugin extends Plugin
} }
catch (IOException e) catch (IOException e)
{ {
getLogger().log(Level.SEVERE, "Failed to start SSH server! ", e); logger.error("Failed to start SSH server! ", e);
} }
logger.info("Loaded Minecraft-SSHD.");
} }
@Override public void onDisable() public static SshdPlugin GetInstance()
{ {
try
{
sshd.stop();
}
catch (Exception e)
{
// do nothing
}
}
public static SshdPlugin getInstance() {
return instance; return instance;
} }
private static void setInstance(SshdPlugin instance) { public Logger GetLogger()
SshdPlugin.instance = instance; {
return this.logger;
} }
} }

View File

@@ -0,0 +1,117 @@
package com.ryanmichela.sshd;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.ErrorHandler;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.layout.PatternLayout;
import jline.console.ConsoleReader;
import org.apache.sshd.common.SshException;
import java.io.IOException;
import java.io.Serializable;
import java.util.UUID;
import java.nio.charset.Charset;
/**
* Copyright 2014 Ryan Michela
*/
public class StreamHandlerAppender implements Appender
{
private ConsoleReader console;
private UUID uuid;
private PatternLayout MinecraftLayout = PatternLayout.newBuilder().withPattern("%highlightError{[%d{HH:mm:ss} %level] [%logger]: %minecraftFormatting{%msg}%xEx}").build();
private PatternLayout MojangLayout = PatternLayout.newBuilder().withPattern("%highlightError{[%d{HH:mm:ss} %level]: %minecraftFormatting{%msg}%xEx}").build();
public StreamHandlerAppender(ConsoleReader console)
{
this.console = console;
uuid = UUID.randomUUID();
}
@Override
public void append(LogEvent logEvent)
{
if (logEvent.getLevel() == Level.DEBUG || logEvent.getLevel() == Level.TRACE)
return;
try
{
// Delete the jline's `> ` character
this.console.print(ConsoleReader.BACKSPACE + "" + ConsoleReader.BACKSPACE);
// Print our message
if (logEvent.getLoggerName().matches("net\\.minecraft\\..*|com\\.mojang\\..*"))
this.console.println(ConsoleLogFormatter.ColorizeString(this.MojangLayout.toSerializable(logEvent)).replaceAll("\n", "\r\n") + "\r");
else
this.console.println(ConsoleLogFormatter.ColorizeString(this.MinecraftLayout.toSerializable(logEvent)).replaceAll("\n", "\r\n") + "\r");
// Reset the console (colors, formatting, etc)
this.console.print(ConsoleReader.RESET_LINE + "");
try
{
// Attempt to draw new console line
this.console.drawLine();
}
catch (Throwable ex)
{
this.console.getCursorBuffer().clear();
}
// Push it to the end user.
this.console.flush();
}
catch (SshException ex)
{
// do nothing
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
@Override
public String getName() {
return "StreamHandlerAppender:" + uuid.toString();
}
@Override
public Layout<? extends Serializable> getLayout() {
return null;
}
@Override
public boolean ignoreExceptions() {
return false;
}
@Override
public ErrorHandler getHandler() {
return null;
}
@Override
public void setHandler(ErrorHandler errorHandler) {
}
@Override
public void start() {
}
@Override
public void stop() {
}
@Override
public boolean isStarted() {
return true;
}
@Override
public boolean isStopped() {
return false;
}
}

View File

@@ -1,40 +1,62 @@
package com.ryanmichela.sshd.implementations; package com.ryanmichela.sshd.implementations;
import com.ryanmichela.sshd.SshdPlugin; import com.ryanmichela.sshd.SshdPlugin;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import com.ryanmichela.sshd.ConsoleShellFactory; import com.ryanmichela.sshd.ConsoleShellFactory;
import com.ryanmichela.sshd.ConsoleLogFormatter; import com.ryanmichela.sshd.ConsoleLogFormatter;
import org.spongepowered.api.command.source.ConsoleSource;
import org.checkerframework.checker.nullness.Opt;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.channel.MessageChannel;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.api.service.permission.SubjectCollection;
import org.spongepowered.api.service.permission.SubjectData;
import org.spongepowered.api.service.permission.SubjectReference;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.util.Tristate;
import jline.console.ConsoleReader;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.HashSet;
import java.util.Collections; import java.util.Set;
import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.Optional;
import java.util.List;
public final class SSHDCommandSender implements CommandSender // This is gonna be a mess.
public class SSHDCommandSender implements ConsoleSource
{ {
private MessageChannel mc = MessageChannel.TO_CONSOLE;
private Subject subjectDelegate;
// Set by the upstream allocating function
public ConsoleShellFactory.ConsoleShell console; public ConsoleShellFactory.ConsoleShell console;
private UUID uuid = UUID.randomUUID();
// This is an override for Sponge to work with the SSH consoles.
@Override @Override
public void sendMessage(Text message)
{
this.sendRawMessage(message.toPlain());
}
// Back port from Spigot/BungeeCord-style API calls.
public void sendMessage(String message) public void sendMessage(String message)
{ {
this.sendRawMessage(message + "\r"); this.sendRawMessage(message);
} }
public void sendRawMessage(String message) public void sendRawMessage(String message)
{ {
// What the fuck does this code even do? Are we sending to one client or all of them?
if (this.console.ConsoleReader == null) if (this.console.ConsoleReader == null)
return; return;
try try
{ {
this.console.ConsoleReader.println(ConsoleLogFormatter.ColorizeString(message).replace("\n", "\n\r")); this.console.ConsoleReader.print(ConsoleReader.BACKSPACE + "" + ConsoleReader.BACKSPACE);
this.console.ConsoleReader.print(this.console.ConsoleReader.RESET_LINE + ""); this.console.ConsoleReader.println(ConsoleLogFormatter.ColorizeString(message).replaceAll("\n", "\n\r") + "\r");
this.console.ConsoleReader.print(ConsoleReader.RESET_LINE + "");
this.console.ConsoleReader.flush(); this.console.ConsoleReader.flush();
try try
{ {
@@ -48,67 +70,109 @@ public final class SSHDCommandSender implements CommandSender
} }
catch (IOException e) catch (IOException e)
{ {
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e); SshdPlugin.GetInstance().logger.error("Error sending message to SSHDCommandSender", e);
} }
} }
@Override public void sendMessage(String[] messages)
public void sendMessages(String... messages)
{ {
Arrays.asList(messages).forEach(this::sendMessage); Arrays.asList(messages).forEach(this::sendMessage);
} }
@Override public MessageChannel getMessageChannel()
public void sendMessage(BaseComponent... message)
{ {
sendMessage(BaseComponent.toLegacyText(message)); return mc;
}
public void setMessageChannel(MessageChannel channel)
{
mc = channel;
}
public String getName() {
return "SSHD Console";
} }
@Override @Override
public void sendMessage(BaseComponent message) public String getIdentifier() {
{ return uuid.toString();
sendMessage(message.toLegacyText());
} }
@Override @Override
public String getName() public Set<Context> getActiveContexts()
{ {
return "SSHD CONSOLE"; // No clue what any of this does but sponge needs it to work with this class. - Justin
Set<Context> set = new HashSet<Context>();
set.add(new Context(Context.USER_KEY, "SSHD"));
return set;
} }
@Override @Override
public Collection<String> getGroups() public boolean isSubjectDataPersisted()
{ {
return Collections.emptySet(); // TODO Auto-generated method stub
return false;
} }
@Override @Override
public void addGroups(String... groups) public boolean isChildOf(Set<Context> contexts, SubjectReference parent)
{ {
throw new UnsupportedOperationException("Console may not have groups"); // TODO Auto-generated method stub
return false;
} }
@Override @Override
public void removeGroups(String... groups) public SubjectData getTransientSubjectData()
{ {
throw new UnsupportedOperationException("Console may not have groups"); // TODO Auto-generated method stub
return null;
} }
@Override @Override
public boolean hasPermission(String permission) public SubjectData getSubjectData()
{ {
return true; // TODO Auto-generated method stub
return null;
} }
@Override @Override
public void setPermission(String permission, boolean value) public Tristate getPermissionValue(Set<Context> contexts, String permission)
{ {
throw new UnsupportedOperationException("Console has all permissions"); // We're allowed to view all permissions.
return Tristate.TRUE;
} }
@Override @Override
public Collection<String> getPermissions() public List<SubjectReference> getParents(Set<Context> contexts)
{ {
return Collections.emptySet(); // TODO Auto-generated method stub
return null;
}
@Override
public Optional<String> getOption(Set<Context> contexts, String key)
{
return Optional.empty();
}
@Override
public SubjectCollection getContainingCollection()
{
// TODO Auto-generated method stub
return null;
}
@Override
public Optional<CommandSource> getCommandSource()
{
// TODO Auto-generated method stub
return Optional.of(this);
}
@Override
public SubjectReference asSubjectReference()
{
// TODO Auto-generated method stub
return null;
} }
} }

View File

@@ -0,0 +1,109 @@
package com.ryanmichela.sshd.utils;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
import org.spongepowered.api.config.DefaultConfig;
import com.google.inject.Inject;
import com.ryanmichela.sshd.SshdPlugin;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;
public class Config
{
// Give us a config!
@Inject
@DefaultConfig(sharedRoot = false)
// idk what to do with this one.
private ConfigurationLoader<CommentedConfigurationNode> configLoader = HoconConfigurationLoader.builder().setPath(SshdPlugin.GetInstance().DefaultConfig).build();
public CommentedConfigurationNode configNode;
public void setup()
{
// I'm not sure if this will even work, the sponge config API is confusing.
if (!Files.exists(SshdPlugin.GetInstance().DefaultConfig))
{
try
{
Files.createFile(SshdPlugin.GetInstance().DefaultConfig);
this.load();
this.populate();
this.save();
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
else
this.load();
}
public void load()
{
try
{
configNode = this.configLoader.load();
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
public void save()
{
try
{
this.configLoader.save(this.configNode);
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
public void populate()
{
this.configNode.getNode("ListenAddress").setValue("all").setComment
("The IP addresses(s) the SSH server will listen on. Use a comma separated list for multiple addresses.\n" +
"Leave as \"all\" for all addresses.");
this.configNode.getNode("Port").setValue("1025").setComment(
"The port the SSH server will listen on. Note that anything above 1024 will require you to run\n" +
"the whole minecraft server with elevated privileges, this is not recommended and you should\n" +
"use iptables to route packets from a lower port.");
this.configNode.getNode("Mode").setValue("DEFAULT").setComment("Operational mode. Don't touch if you don't know what you're doing. Can be either DEFAULT or RPC");
this.configNode.getNode("EnableSFTP").setValue("true").setComment(
"Enable built-in SFTP server or not. You'll be able to connect and upload/download files via SFTP protocol.\n" +
"Might be useful for testing purposes as well , i. e. docker containers.");
this.configNode.getNode("LoginRetries").setValue("3").setComment(
"Number of times a person can fail to use an SSH key or enter a password\n" +
"before it terminates the connection.");
this.configNode.getNode("PasswordType").setValue("bcrypt").setComment
("########################################################################################\n" +
"By default, only public key authentication is enabled. This is the most secure mode.\n" +
"To authorize a user to login with their public key, install their key using the\n" +
"OpenSSH authorized_keys file format in the authorized_users directory. Name the key\n" +
"file with the user's username and no extension. Note: If you want to let a user have\n" +
"many keys, you can append the keys to their file in authorized_users.\n" +
"########################################################################################\n" +
"For less secure username and password based authentication, complete the sections below.\n" +
"\n" +
"Type of hashing to use for the passwords below.\n" +
"Options are: PLAIN (insecure), bcrypt, pbkdf2, sha256\n" +
"\n" +
"You can use the console/in-game command `/mkpasswd [hash] PASSWORD` to\n" +
"generate a password hash string then copy it for your passwords below.\n" +
"You can also use `/mkpasswd help` to see what algorithms are supported.");
this.configNode.getNode("Credentials").setComment("Associate each username with a password hash (or the password if the PasswordType is set to PLAIN)");
this.configNode.getNode("Credentials", "user1", "password").setValue("MySecretPassword");
this.configNode.getNode("Credentials", "user2", "password").setValue("MyBestFriendsPassword");
}
}

View File

@@ -1,350 +0,0 @@
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) {
return SshdPlugin.instance != null && SshdPlugin.instance.getLogger().isLoggable(level);
}
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);
}
}
}

View File

@@ -1,82 +0,0 @@
/**
* 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&uuml;lc&uuml;
*/
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;
}
}

View File

@@ -1,8 +0,0 @@
name: SSHD
version: ${project.version}
author: Ryan Michela, Haarolean, toxuin, Justin Crawford, Zachery Coleman
main: com.ryanmichela.sshd.SshdPlugin
commands:
mkpasswd:
description: Make a SSHD password hash
usage: /mkpasswd <hash|help> <password>