19 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
Justin Crawford
e986cacc50 Update readme. 2019-10-07 23:24:32 -07:00
Justin Crawford
0afba39d57 This should actually fix rmichela#10 2019-10-06 02:58:52 -07:00
Justin Crawford
1b4c7c2304 Update readme as it's wildly out of date 2019-10-06 01:07:39 -07:00
Justin Crawford
4a39525809 Added 3 new password hashing schemes
Support for bcrypt, sha256, pbkdf2 and plain text ofc.
This makes password-based auth actually secure.
2019-10-06 00:11:13 -07:00
Justin Crawford
73c30f5214 You can now configure the MOTD, also fixed an old bug.
You can customize the MOTD you see when you login (with the exception
of what server you're logged into and some other info) and this
includes colorization support via minecraft color codes (you need the
special color code character that minecraft uses though, the example
motd.txt has an included rainbow you can work from.)

Hopefully fixed an old bug with the logger writting to the disconnected
client session which causes a ton of exceptions because the session is gone.
2019-10-04 23:27:31 -07:00
Justin Crawford
3e45f7ebf4 Tweak the SSH MOTD a bit 2019-10-03 22:16:29 -07:00
Justin Crawford
0e05bb61bc Support CTRL+D for exiting the console
Support CTRL+D for exiting the console, also support "cls" for
clearing the screen (on supported terminals).
2019-10-03 21:41:02 -07:00
Justin Crawford
0635ea7a35 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.
2019-10-03 21:07:00 -07:00
Justin Crawford
25287b1580 Fix Travis 2019-10-02 19:41:31 -07:00
Justin Crawford
0458179597 Add support for authorized_keys files.
Each user can have a set of authorized keys for public key authentication.
This is better to support as it lets us use different algorithms and not
just RSA. In the age of security, it's good to have variety.

I also added additional libraries to support ed25519-based public keys.

I updated the SSH libraries so any upstream bug fixes are applied, fixed
some warnings and a few other things.
2019-10-02 19:14:56 -07:00
34 changed files with 2686 additions and 1365 deletions

68
.clangformat Normal file
View File

@@ -0,0 +1,68 @@
---
#BasedOnStyle: WebKit
TabWidth: '4'
IndentWidth: '4'
UseTab: 'Always'
AlignOperands: 'true'
AlignAfterOpenBracket: 'Align'
AlignConsecutiveAssignments: 'true'
AlignConsecutiveDeclarations: 'true'
AlignEscapedNewlines: 'Left'
AlignTrailingComments: 'true'
AllowAllParametersOfDeclarationOnNextLine: 'true'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: 'All'
AllowShortIfStatementsOnASingleLine: 'false'
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterReturnType: 'None'
AlwaysBreakTemplateDeclarations: 'true'
AlwaysBreakBeforeMultilineStrings: 'false'
BinPackArguments: 'false'
BinPackParameters: 'false'
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'
BreakBeforeTernaryOperators: 'false'
BreakConstructorInitializersBeforeComma: 'false'
BreakBeforeInheritanceComma: 'false'
BreakStringLiterals: 'true'
ColumnLimit: '140'
CompactNamespaces: 'false'
Cpp11BracedListStyle: 'true'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
DerivePointerAlignment: 'false'
IndentCaseLabels: 'true'
IndentPPDirectives: 'AfterHash'
KeepEmptyLinesAtTheStartOfBlocks: 'true'
Language: 'Java'
NamespaceIndentation: 'All'
PointerAlignment: 'Right'
ReflowComments: 'true'
SortIncludes: 'true'
SortUsingDeclarations: 'true'
SpaceAfterCStyleCast: 'false'
SpaceAfterTemplateKeyword: 'false'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: 'false'
SpacesInAngles: 'false'
SpacesInCStyleCastParentheses: 'false'
SpacesInContainerLiterals: 'false'
SpacesInParentheses: 'false'
SpacesInSquareBrackets: 'false'
Standard: 'Auto'
...

View File

@@ -1,4 +1,5 @@
sudo: false
dist: trusty
language: java
jdk:
- oraclejdk8

100
README.md
View File

@@ -1,6 +1,100 @@
Bukkit-SSHD
Minecraft-SSHD (Sponge Edition!)
===========
[![Build Status](https://travis-ci.org/rmichela/Bukkit-SSHD.png)](https://travis-ci.org/rmichela/Bukkit-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)
[![GitHub license](https://img.shields.io/github/license/Justasic/Minecraft-SSHD)](https://github.com/Justasic/Minecraft-SSHD/blob/master/LICENSE)
An SSHD daemon embedded in a Bukkit plugin.
<img align="left" width="140" height="140" src="docs/ssh_logo.png?raw=true" hspace="5" vspace="5" alt="diskover"><br/>
**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 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.
- Remotely view your server log in real-time.
- Remotely issue commands from the server console, just as if you were on the server itself.
- Supports multiple concurrent remote connections.
- Strong identity support using public key authentication.
- Audit history who is running commands in the console
- Run Spigot without using screen or tmux (by adding `-noconsole`)
- Remotely script your server by issuing one-off console commands with ssh.
### Why should I use Minecraft-SSHD?
- 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 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 just want to access your server console using SSH.
Note: By default, only public key authentication is enabled. This is the most secure authentication mode! Setting a username and password will make your server less secure.
Screenshots
============
<img align="left" width="390" src="docs/console.png?raw=true" hspace="5" vspace="5" alt="console">
<img width="400" src="docs/session.png?raw=true" alt="session"><br>
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 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
#### On Windows
1. Ensure [Putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) is installed and open up `puttygen` (you can search for it in start search).
2. Click `Generate` and follow the directions.
3. When it finishes, set your key comment (if you like) and copy the text from the big `Public key for pasting into OpenSSH authorized_keys file`
4. Create a new file inside of the `plugins/SSHD/authorized_users` folder and name the file just the username (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
5. Paste the key you copied from step 3 into the file you just created.
6. SSH into the server and see if your key works
#### On Linux/OS X
1. Open a terminal and run `ssh-keygen` then follow the prompts.
2. Copy the contents of your `id_<algorithm>.pub` file (example: if your key was generated with rsa, it will be named `id_rsa.pub`). This file is usually located in `/home/YOURUSERNAME/.ssh/`
3. Paste the contents of the .pub file into a new file inside the `plugins/SSHD/authorized_users` folder and name the file just the username that the user will use to login with (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
## Using existing keys
#### On Windows
1. Ensure [Putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) is installed and open up `puttygen` (you can search for it in start search).
2. Click `Conversions` then click `Import Key` and select your .ppk file.
3. Copy the text from the big `Public key for pasting into OpenSSH authorized_keys file`
4. Create a new file inside of the `plugins/SSHD/authorized_users` folder and name the file just the username (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
5. Paste the key you copied from step 3 into the file you just created.
6. SSH into the server and see if your key works
#### On Linux/OS X
1. Copy the contents of your `id_<algorithm>.pub` file (example: if your key was generated with rsa, it will be named `id_rsa.pub`). This file is usually located in `/home/YOURUSERNAME/.ssh/`
2. Paste the contents of the .pub file into a new file inside the `plugins/SSHD/authorized_users` folder and name the file just the username that the user will use to login with (example: `justasic`, there should ***NOT*** be a file extension or authentication does not work).
Plugin Usage
============
## Commands
/mkpasswd <hash|help> <password>
mkpasswd supports the following hash algorithms:
- bcrypt - Using the OpenBSD-style Blowfish password hash
- sha256 - Using a basic salted sha256 hash
- pbkdf2 - Using the [PBKDF2](https://en.wikipedia.org/wiki/Pbkdf2) password hash
- PLAIN - Using plain text passwords (very insecure)
## Permissions
`sshd.mkpasswd` - Checks if the in-game user has access to run the mkpasswd command.
Minecraft-SSHD uses cryptographic certificates or a secure username and password to verify remote access.
## Source Code
[Get the source on GitHub](https://github.com/Justasic/Minecraft-SSHD "Source Code")

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>

BIN
docs/console.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

BIN
docs/session.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

BIN
docs/ssh_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

133
docs/ssh_logo.svg Normal file
View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="800"
height="800"
viewBox="0 0 211.66666 211.66667"
version="1.1"
id="svg8"
sodipodi:docname="ssh_logo.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<defs
id="defs2">
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter5273">
<feFlood
flood-opacity="1"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood5263" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite5265" />
<feGaussianBlur
in="composite1"
stdDeviation="3.5"
result="blur"
id="feGaussianBlur5267" />
<feOffset
dx="1"
dy="1"
result="offset"
id="feOffset5269" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite5271" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="375.64503"
inkscape:cy="367.3313"
inkscape:document-units="mm"
inkscape:current-layer="layer3"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1920"
inkscape:window-y="119"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Background"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-85.333343)">
<circle
style="fill:#4c4c4c;fill-opacity:1;stroke-width:0.30369404;filter:url(#filter5273)"
id="path4533"
cx="105"
cy="192.00002"
r="100" />
<circle
style="fill:#242424;fill-opacity:1;stroke-width:0.27332464;"
id="path4533-3"
cx="105"
cy="192.00002"
r="90" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Text"
style="opacity:0.98999999">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:145.88342285px;line-height:0;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.31260732"
x="130.4019"
y="91.524567"
id="text5759"
transform="scale(0.84637592,1.1815081)"><tspan
sodipodi:role="line"
id="tspan5757"
x="130.4019"
y="91.524567"
style="font-size:145.88342285px;line-height:0;fill:#ffffff;fill-opacity:1;stroke-width:0.31260732">_</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:145.88342285px;line-height:0;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;opacity:0.98999999;fill:#20bb00;fill-opacity:1;stroke:none;stroke-width:0.31260732"
x="39.53738"
y="118.43275"
id="text5759-2"
transform="scale(0.84637592,1.1815081)"><tspan
sodipodi:role="line"
id="tspan5757-5"
x="39.53738"
y="118.43275"
style="font-size:123.47222137px;line-height:0;fill:#20bb00;fill-opacity:1;stroke-width:0.31260732">&gt;</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

95
pom.xml
View File

@@ -1,62 +1,59 @@
<?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"
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ryanmichela</groupId>
<artifactId>SSHD</artifactId>
<version>1.3.4.1</version>
<url>http://dev.bukkit.org/server-mods/sshd/</url>
<artifactId>sshd</artifactId>
<version>2.0.0</version>
<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>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!-- Repositories -->
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public</url>
<id>sponge</id>
<url>https://repo.spongepowered.org/maven</url>
</repository>
</repositories>
<!-- License -->
<licenses>
<license>
<name>GPL2</name>
<url>http://www.gnu.org/licenses/gpl-2.0.html</url>
</license>
</licenses>
<!-- Dependencies -->
<dependencies>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.12.2-R0.1-SNAPSHOT</version>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<version>1.6.0</version>
<scope>compile</scope>
<type>jar</type>
<artifactId>sshd-mina</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.0.16</version>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-common</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-sftp</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
<groupId>net.i2p.crypto</groupId>
<artifactId>eddsa</artifactId>
<version>0.3.0</version>
</dependency>
<dependency>
@@ -68,43 +65,45 @@
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.0</version>
<scope>provided</scope>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<!-- include Sponge API -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
<groupId>org.spongepowered</groupId>
<artifactId>spongeapi</artifactId>
<version>7.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- Build -->
<build>
<!-- Set our default goal to be clean and build a package -->
<defaultGoal>clean package</defaultGoal>
<!-- Include our git hash in the final name -->
<finalName>${project.artifactId}-${project.version}</finalName>
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>plugin.yml</include>
<include>config.yml</include>
<include>motd.txt</include>
</includes>
</resource>
</resources>
<!-- Plugins -->
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<version>3.1.1</version>
<executions>
<execution>
<phase>package</phase>
@@ -114,7 +113,7 @@
</execution>
</executions>
<configuration>
<finalName>${project.name}-${project.version}</finalName>
<finalName>${project.artifactId}-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
@@ -126,7 +125,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -135,6 +134,4 @@
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
</project>

View File

@@ -0,0 +1,752 @@
package com.ryanmichela.sshd;
// Copyright (c) 2006 Damien Miller <djm@mindrot.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
/**
* BCrypt implements OpenBSD-style Blowfish password hashing using
* the scheme described in "A Future-Adaptable Password Scheme" by
* Niels Provos and David Mazieres.
* <p>
* This password hashing system tries to thwart off-line password
* cracking using a computationally-intensive hashing algorithm,
* based on Bruce Schneier's Blowfish cipher. The work factor of
* the algorithm is parameterised, so it can be increased as
* computers get faster.
* <p>
* Usage is really simple. To hash a password for the first time,
* call the hashpw method with a random salt, like this:
* <p>
* <code>
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br />
* </code>
* <p>
* To check whether a plaintext password matches one that has been
* hashed previously, use the checkpw method:
* <p>
* <code>
* if (BCrypt.checkpw(candidate_password, stored_hash))<br />
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It matches");<br />
* else<br />
* &nbsp;&nbsp;&nbsp;&nbsp;System.out.println("It does not match");<br />
* </code>
* <p>
* The gensalt() method takes an optional parameter (log_rounds)
* that determines the computational complexity of the hashing:
* <p>
* <code>
* String strong_salt = BCrypt.gensalt(10)<br />
* String stronger_salt = BCrypt.gensalt(12)<br />
* </code>
* <p>
* The amount of work increases exponentially (2**log_rounds), so
* each increment is twice as much work. The default log_rounds is
* 10, and the valid range is 4 to 31.
*
* @author Damien Miller
* @version 0.2
*/
public class BCrypt {
// BCrypt parameters
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
private static final int BCRYPT_SALT_LEN = 16;
// Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16;
// Initial contents of key schedule
private static final int P_orig[] = {
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b
};
private static final int S_orig[] = {
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
};
// bcrypt IV: "OrpheanBeholderScryDoubt"
static private final int bf_crypt_ciphertext[] = {
0x4f727068, 0x65616e42, 0x65686f6c,
0x64657253, 0x63727944, 0x6f756274
};
// Table for Base64 encoding
static private final char base64_code[] = {
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9'
};
// Table for Base64 decoding
static private final byte index_64[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
-1, -1, -1, -1, -1, -1, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, -1, -1, -1, -1, -1
};
// Expanded Blowfish key
private int P[];
private int S[];
/**
* Encode a byte array using bcrypt's slightly-modified base64
* encoding scheme. Note that this is *not* compatible with
* the standard MIME-base64 encoding.
*
* @param d the byte array to encode
* @param len the number of bytes to encode
* @return base64-encoded string
* @exception IllegalArgumentException if the length is invalid
*/
private static String encode_base64(byte d[], int len)
throws IllegalArgumentException {
int off = 0;
StringBuffer rs = new StringBuffer();
int c1, c2;
if (len <= 0 || len > d.length)
throw new IllegalArgumentException ("Invalid len");
while (off < len) {
c1 = d[off++] & 0xff;
rs.append(base64_code[(c1 >> 2) & 0x3f]);
c1 = (c1 & 0x03) << 4;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 4) & 0x0f;
rs.append(base64_code[c1 & 0x3f]);
c1 = (c2 & 0x0f) << 2;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 6) & 0x03;
rs.append(base64_code[c1 & 0x3f]);
rs.append(base64_code[c2 & 0x3f]);
}
return rs.toString();
}
/**
* Look up the 3 bits base64-encoded by the specified character,
* range-checking againt conversion table
* @param x the base64-encoded value
* @return the decoded value of x
*/
private static byte char64(char x) {
if ((int)x < 0 || (int)x > index_64.length)
return -1;
return index_64[(int)x];
}
/**
* Decode a string encoded using bcrypt's base64 scheme to a
* byte array. Note that this is *not* compatible with
* the standard MIME-base64 encoding.
* @param s the string to decode
* @param maxolen the maximum number of bytes to decode
* @return an array containing the decoded bytes
* @throws IllegalArgumentException if maxolen is invalid
*/
private static byte[] decode_base64(String s, int maxolen)
throws IllegalArgumentException {
StringBuffer rs = new StringBuffer();
int off = 0, slen = s.length(), olen = 0;
byte ret[];
byte c1, c2, c3, c4, o;
if (maxolen <= 0)
throw new IllegalArgumentException ("Invalid maxolen");
while (off < slen - 1 && olen < maxolen) {
c1 = char64(s.charAt(off++));
c2 = char64(s.charAt(off++));
if (c1 == -1 || c2 == -1)
break;
o = (byte)(c1 << 2);
o |= (c2 & 0x30) >> 4;
rs.append((char)o);
if (++olen >= maxolen || off >= slen)
break;
c3 = char64(s.charAt(off++));
if (c3 == -1)
break;
o = (byte)((c2 & 0x0f) << 4);
o |= (c3 & 0x3c) >> 2;
rs.append((char)o);
if (++olen >= maxolen || off >= slen)
break;
c4 = char64(s.charAt(off++));
o = (byte)((c3 & 0x03) << 6);
o |= c4;
rs.append((char)o);
++olen;
}
ret = new byte[olen];
for (off = 0; off < olen; off++)
ret[off] = (byte)rs.charAt(off);
return ret;
}
/**
* Blowfish encipher a single 64-bit block encoded as
* two 32-bit halves
* @param lr an array containing the two 32-bit half blocks
* @param off the position in the array of the blocks
*/
private final void encipher(int lr[], int off) {
int i, n, l = lr[off], r = lr[off + 1];
l ^= P[0];
for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
// Feistel substitution on left word
n = S[(l >> 24) & 0xff];
n += S[0x100 | ((l >> 16) & 0xff)];
n ^= S[0x200 | ((l >> 8) & 0xff)];
n += S[0x300 | (l & 0xff)];
r ^= n ^ P[++i];
// Feistel substitution on right word
n = S[(r >> 24) & 0xff];
n += S[0x100 | ((r >> 16) & 0xff)];
n ^= S[0x200 | ((r >> 8) & 0xff)];
n += S[0x300 | (r & 0xff)];
l ^= n ^ P[++i];
}
lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1];
lr[off + 1] = l;
}
/**
* Cycically extract a word of key material
* @param data the string to extract the data from
* @param offp a "pointer" (as a one-entry array) to the
* current offset into data
* @return the next word of material from data
*/
private static int streamtoword(byte data[], int offp[]) {
int i;
int word = 0;
int off = offp[0];
for (i = 0; i < 4; i++) {
word = (word << 8) | (data[off] & 0xff);
off = (off + 1) % data.length;
}
offp[0] = off;
return word;
}
/**
* Initialise the Blowfish key schedule
*/
private void init_key() {
P = (int[])P_orig.clone();
S = (int[])S_orig.clone();
}
/**
* Key the Blowfish cipher
* @param key an array containing the key
*/
private void key(byte key[]) {
int i;
int koffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
P[i] = P[i] ^ streamtoword(key, koffp);
for (i = 0; i < plen; i += 2) {
encipher(lr, 0);
P[i] = lr[0];
P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
encipher(lr, 0);
S[i] = lr[0];
S[i + 1] = lr[1];
}
}
/**
* Perform the "enhanced key schedule" step described by
* Provos and Mazieres in "A Future-Adaptable Password Scheme"
* http://www.openbsd.org/papers/bcrypt-paper.ps
* @param data salt information
* @param key password information
*/
private void ekskey(byte data[], byte key[]) {
int i;
int koffp[] = { 0 }, doffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
P[i] = P[i] ^ streamtoword(key, koffp);
for (i = 0; i < plen; i += 2) {
lr[0] ^= streamtoword(data, doffp);
lr[1] ^= streamtoword(data, doffp);
encipher(lr, 0);
P[i] = lr[0];
P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
lr[0] ^= streamtoword(data, doffp);
lr[1] ^= streamtoword(data, doffp);
encipher(lr, 0);
S[i] = lr[0];
S[i + 1] = lr[1];
}
}
/**
* Perform the central password hashing step in the
* bcrypt scheme
* @param password the password to hash
* @param salt the binary salt to hash with the password
* @param log_rounds the binary logarithm of the number
* of rounds of hashing to apply
* @return an array containing the binary hashed password
*/
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds) {
int rounds, i, j;
int cdata[] = (int[])bf_crypt_ciphertext.clone();
int clen = cdata.length;
byte ret[];
if (log_rounds < 4 || log_rounds > 31)
throw new IllegalArgumentException ("Bad number of rounds");
rounds = 1 << log_rounds;
if (salt.length != BCRYPT_SALT_LEN)
throw new IllegalArgumentException ("Bad salt length");
init_key();
ekskey(salt, password);
for (i = 0; i < rounds; i++) {
key(password);
key(salt);
}
for (i = 0; i < 64; i++) {
for (j = 0; j < (clen >> 1); j++)
encipher(cdata, j << 1);
}
ret = new byte[clen * 4];
for (i = 0, j = 0; i < clen; i++) {
ret[j++] = (byte)((cdata[i] >> 24) & 0xff);
ret[j++] = (byte)((cdata[i] >> 16) & 0xff);
ret[j++] = (byte)((cdata[i] >> 8) & 0xff);
ret[j++] = (byte)(cdata[i] & 0xff);
}
return ret;
}
/**
* Hash a password using the OpenBSD bcrypt scheme
* @param password the password to hash
* @param salt the salt to hash with (perhaps generated
* using BCrypt.gensalt)
* @return the hashed password
*/
public static String hashpw(String password, String salt) {
BCrypt B;
String real_salt;
byte passwordb[], saltb[], hashed[];
char minor = (char)0;
int rounds, off = 0;
StringBuffer rs = new StringBuffer();
if (salt.charAt(0) != '$' || salt.charAt(1) != '2')
throw new IllegalArgumentException ("Invalid salt version");
if (salt.charAt(2) == '$')
off = 3;
else {
minor = salt.charAt(2);
if (minor != 'a' || salt.charAt(3) != '$')
throw new IllegalArgumentException ("Invalid salt revision");
off = 4;
}
// Extract number of rounds
if (salt.charAt(off + 2) > '$')
throw new IllegalArgumentException ("Missing salt rounds");
rounds = Integer.parseInt(salt.substring(off, off + 2));
real_salt = salt.substring(off + 3, off + 25);
try {
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
throw new AssertionError("UTF-8 is not supported");
}
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
B = new BCrypt();
hashed = B.crypt_raw(passwordb, saltb, rounds);
rs.append("$2");
if (minor >= 'a')
rs.append(minor);
rs.append("$");
if (rounds < 10)
rs.append("0");
rs.append(Integer.toString(rounds));
rs.append("$");
rs.append(encode_base64(saltb, saltb.length));
rs.append(encode_base64(hashed,
bf_crypt_ciphertext.length * 4 - 1));
return rs.toString();
}
/**
* Generate a salt for use with the BCrypt.hashpw() method
* @param log_rounds the log2 of the number of rounds of
* hashing to apply - the work factor therefore increases as
* 2**log_rounds.
* @param random an instance of SecureRandom to use
* @return an encoded salt value
*/
public static String gensalt(int log_rounds, SecureRandom random) {
StringBuffer rs = new StringBuffer();
byte rnd[] = new byte[BCRYPT_SALT_LEN];
random.nextBytes(rnd);
rs.append("$2a$");
if (log_rounds < 10)
rs.append("0");
rs.append(Integer.toString(log_rounds));
rs.append("$");
rs.append(encode_base64(rnd, rnd.length));
return rs.toString();
}
/**
* Generate a salt for use with the BCrypt.hashpw() method
* @param log_rounds the log2 of the number of rounds of
* hashing to apply - the work factor therefore increases as
* 2**log_rounds.
* @return an encoded salt value
*/
public static String gensalt(int log_rounds) {
return gensalt(log_rounds, new SecureRandom());
}
/**
* Generate a salt for use with the BCrypt.hashpw() method,
* selecting a reasonable default for the number of hashing
* rounds to apply
* @return an encoded salt value
*/
public static String gensalt() {
return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
}
/**
* Check that a plaintext password matches a previously hashed
* one
* @param plaintext the plaintext password to verify
* @param hashed the previously-hashed password
* @return true if the passwords match, false otherwise
*/
public static boolean checkpw(String plaintext, String hashed) {
return (hashed.compareTo(hashpw(plaintext, hashed)) == 0);
}
}

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

@@ -3,6 +3,8 @@ package com.ryanmichela.sshd;
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import com.ryanmichela.sshd.Cryptography;
import java.util.HashMap;
import java.util.Map;
@@ -11,28 +13,82 @@ import java.util.Map;
*/
public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
private Map<String, Integer> failCounts = new HashMap<String, Integer>();
private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
@Override
public boolean authenticate(String username, String password, ServerSession serverSession) {
if (SshdPlugin.instance.getConfig().getString("credentials." + username).equals(password)) {
failCounts.put(username, 0);
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.
String HashType = SshdPlugin.GetInstance().PasswordType;
String ConfigHash = SshdPlugin.GetInstance().config.configNode.getNode("Credentials", username.trim(), "password").getString();
if (ConfigHash == null)
SshdPlugin.GetInstance().logger.warn("Config has no such user: " + username);
else
{
try
{
if (HashType.equalsIgnoreCase("PLAIN"))
{
if (ConfigHash.equals(password))
{
FailCounts.put(username, 0);
return true;
}
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using password authentication.");
}
else if (HashType.equalsIgnoreCase("bcrypt"))
{
if (Cryptography.BCrypt_ValidatePassword(password, ConfigHash))
{
FailCounts.put(username, 0);
return true;
}
}
else if (HashType.equalsIgnoreCase("pbkdf2"))
{
if (Cryptography.PBKDF2_ValidateHash(password, ConfigHash))
{
FailCounts.put(username, 0);
return true;
}
}
else if (HashType.equalsIgnoreCase("sha256"))
{
if (Cryptography.SHA256_ValidatePassword(password, ConfigHash))
{
FailCounts.put(username, 0);
return true;
}
}
}
catch (Exception e)
{
// report it to the console.
e.printStackTrace();
// But continue as though there was a password failure.
}
}
try {
SshdPlugin.GetInstance().logger.info("Failed login for " + username + " using " + HashType + "-based password authentication.");
Integer tries = SshdPlugin.GetInstance().LoginRetries;
try
{
Thread.sleep(3000);
if (failCounts.containsKey(username)) {
failCounts.put(username, failCounts.get(username) + 1);
} else {
failCounts.put(username, 1);
if (this.FailCounts.containsKey(username))
this.FailCounts.put(username, this.FailCounts.get(username) + 1);
else
this.FailCounts.put(username, 1);
if (this.FailCounts.get(username) >= tries)
{
this.FailCounts.put(username, 0);
SshdPlugin.GetInstance().logger.info("Too many failures for " + username + ", disconnecting.");
ss.close(true);
}
if (failCounts.get(username) >= 3) {
failCounts.put(username, 0);
serverSession.close(true);
}
} catch (InterruptedException e) {
catch (InterruptedException e)
{
// do nothing
}
return false;

View File

@@ -5,40 +5,61 @@ package com.ryanmichela.sshd;
*/
import jline.console.completer.Completer;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandMap;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.scheduler.SpongeExecutorService;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
public class ConsoleCommandCompleter implements Completer {
public class ConsoleCommandCompleter implements Completer
{
private SpongeExecutorService MinecraftExecutor;
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
Waitable<List<String>> waitable = new Waitable<List<String>>() {
public ConsoleCommandCompleter()
{
super();
this.MinecraftExecutor = Sponge.getScheduler().createSyncExecutor(SshdPlugin.GetInstance());
}
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates)
{
Waitable<List<String>> waitable = new Waitable<List<String>>()
{
@Override
protected List<String> evaluate() {
CommandMap commandMap = ReflectionUtil.getProtectedValue(Bukkit.getServer(), "commandMap");
return commandMap.tabComplete(Bukkit.getServer().getConsoleSender(), buffer);
protected List<String> evaluate()
{
return Sponge.getCommandManager().getSuggestions(Sponge.getServer().getConsole(), buffer, null);
}
};
Bukkit.getScheduler().runTask(SshdPlugin.instance, waitable);
try {
this.MinecraftExecutor.execute(waitable);
try
{
List<String> offers = waitable.get();
if (offers == null) {
if (offers == null)
{
return cursor;
}
candidates.addAll(offers);
final int lastSpace = buffer.lastIndexOf(' ');
if (lastSpace == -1) {
if (lastSpace == -1)
{
return cursor - buffer.length();
} else {
}
else
{
return cursor - (buffer.length() - lastSpace - 1);
}
} catch (ExecutionException e) {
SshdPlugin.instance.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e);
} catch (InterruptedException e) {
}
catch (ExecutionException e)
{
SshdPlugin.GetInstance().logger.warn("Unhandled exception when tab completing", e);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
return cursor;

View File

@@ -1,10 +1,11 @@
package com.ryanmichela.sshd;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.command.CommandFactory;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.bukkit.Bukkit;
import org.spongepowered.api.Sponge;
import java.io.IOException;
import java.io.InputStream;
@@ -16,7 +17,7 @@ import java.io.OutputStream;
public class ConsoleCommandFactory implements CommandFactory {
@Override
public Command createCommand(String command) {
public Command createCommand(ChannelSession cs, String command) {
return new ConsoleCommand(command);
}
@@ -50,21 +51,26 @@ public class ConsoleCommandFactory implements CommandFactory {
}
@Override
public void start(Environment environment) throws IOException {
try {
SshdPlugin.instance.getLogger()
public void start(ChannelSession cs, Environment environment) throws IOException
{
try
{
SshdPlugin.GetInstance().logger
.info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
} catch (Exception e) {
SshdPlugin.instance.getLogger().severe("Error processing command from SSH -" + e.getMessage());
} finally {
Sponge.getCommandManager().process(Sponge.getServer().getConsole(), command);
}
catch (Exception e)
{
SshdPlugin.GetInstance().logger.error("Error processing command from SSH -" + e.getMessage());
}
finally
{
callback.onExit(0);
}
}
@Override
public void destroy() {
}
public void destroy(ChannelSession cn) {}
}
}

View File

@@ -4,79 +4,36 @@ package com.ryanmichela.sshd;
* Copyright 2013 Ryan Michela
*/
import org.bukkit.ChatColor;
import com.ryanmichela.sshd.ChatColor;
import org.fusesource.jansi.Ansi;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.EnumMap;
import java.util.Map;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
public class ConsoleLogFormatter extends Formatter {
public class ConsoleLogFormatter
{
private static final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
private SimpleDateFormat dateFormat;
public ConsoleLogFormatter() {
this.dateFormat = new SimpleDateFormat("HH:mm:ss");
}
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();
}
private void colorize(LogRecord logrecord) {
public static String ColorizeString(String str)
{
// ORIGINAL CODE FROM org.bukkit.craftbukkit.command.ColouredConsoleSender
final Map<ChatColor, String> replacements = new EnumMap<>(ChatColor.class);
replacements
.put(ChatColor.BLACK, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).boldOff().toString());
replacements
.put(ChatColor.DARK_BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).boldOff().toString());
replacements.put(ChatColor.DARK_GREEN,
Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).boldOff().toString());
replacements
.put(ChatColor.DARK_AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).boldOff().toString());
replacements
.put(ChatColor.DARK_RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).boldOff().toString());
replacements.put(ChatColor.DARK_PURPLE,
Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).boldOff().toString());
replacements
.put(ChatColor.GOLD, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).boldOff().toString());
replacements.put(ChatColor.BLACK, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).boldOff().toString());
replacements.put(ChatColor.DARK_BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).boldOff().toString());
replacements.put(ChatColor.DARK_GREEN, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).boldOff().toString());
replacements.put(ChatColor.DARK_AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).boldOff().toString());
replacements.put(ChatColor.DARK_RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).boldOff().toString());
replacements.put(ChatColor.DARK_PURPLE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).boldOff().toString());
replacements.put(ChatColor.GOLD, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).boldOff().toString());
replacements.put(ChatColor.GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.WHITE).boldOff().toString());
replacements
.put(ChatColor.DARK_GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).bold().toString());
replacements.put(ChatColor.DARK_GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).bold().toString());
replacements.put(ChatColor.BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).bold().toString());
replacements.put(ChatColor.GREEN, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).bold().toString());
replacements.put(ChatColor.AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).bold().toString());
replacements.put(ChatColor.RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).bold().toString());
replacements.put(ChatColor.LIGHT_PURPLE,
Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).bold().toString());
replacements.put(ChatColor.LIGHT_PURPLE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).bold().toString());
replacements.put(ChatColor.YELLOW, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).bold().toString());
replacements.put(ChatColor.WHITE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.WHITE).bold().toString());
replacements.put(ChatColor.MAGIC, Ansi.ansi().a(Ansi.Attribute.BLINK_SLOW).toString());
@@ -86,16 +43,20 @@ public class ConsoleLogFormatter extends Formatter {
replacements.put(ChatColor.ITALIC, Ansi.ansi().a(Ansi.Attribute.ITALIC).toString());
replacements.put(ChatColor.RESET, Ansi.ansi().a(Ansi.Attribute.RESET).toString());
String result = logrecord.getMessage();
for (ChatColor color : ChatColor.values()) {
if (replacements.containsKey(color)) {
String result = str;
for (ChatColor color : ChatColor.values())
{
if (replacements.containsKey(color))
{
result = result.replaceAll("(?i)" + color.toString(), replacements.get(color));
} else {
}
else
{
result = result.replaceAll("(?i)" + color.toString(), "");
}
}
result += Ansi.ansi().reset().toString();
logrecord.setMessage(result);
return result;
}
}

View File

@@ -1,34 +1,48 @@
package com.ryanmichela.sshd;
import com.ryanmichela.sshd.ConsoleCommandCompleter;
import com.ryanmichela.sshd.ConsoleLogFormatter;
import com.ryanmichela.sshd.FlushyOutputStream;
import com.ryanmichela.sshd.SshTerminal;
import com.ryanmichela.sshd.SshdPlugin;
import com.ryanmichela.sshd.StreamHandlerAppender;
import com.ryanmichela.sshd.implementations.SSHDCommandSender;
import com.ryanmichela.sshd.ConsoleLogFormatter;
import jline.console.ConsoleReader;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.apache.sshd.common.Factory;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.shell.ShellFactory;
import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.bukkit.Bukkit;
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.FileNotFoundException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.StreamHandler;
public class ConsoleShellFactory implements Factory<Command> {
public class ConsoleShellFactory implements ShellFactory {
static SSHDCommandSender sshdCommandSender = new SSHDCommandSender();
public Command get() {
return this.create();
}
public Command create() {
public Command createShell(ChannelSession cs) {
return new ConsoleShell();
}
public static class ConsoleShell implements Command, Runnable {
public class ConsoleShell implements Command, Runnable {
private InputStream in;
private OutputStream out;
@@ -36,9 +50,11 @@ public class ConsoleShellFactory implements Factory<Command> {
private ExitCallback callback;
private Environment environment;
private Thread thread;
private String Username;
StreamHandlerAppender streamHandlerAppender;
public static ConsoleReader consoleReader;
public ConsoleReader ConsoleReader;
public SSHDCommandSender SshdCommandSender;
public InputStream getIn() {
return in;
@@ -72,69 +88,139 @@ public class ConsoleShellFactory implements Factory<Command> {
this.callback = callback;
}
public void start(Environment env) throws IOException {
try {
consoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal());
consoleReader.setExpandEvents(true);
consoleReader.addCompleter(new ConsoleCommandCompleter());
@Override
public void start(ChannelSession cs, Environment env) throws IOException
{
try
{
this.ConsoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal());
this.ConsoleReader.setExpandEvents(true);
this.ConsoleReader.addCompleter(new ConsoleCommandCompleter());
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), consoleReader);
streamHandlerAppender = new StreamHandlerAppender(streamHandler);
this.streamHandlerAppender = new StreamHandlerAppender(this.ConsoleReader);
((Logger) LogManager.getRootLogger()).addAppender(streamHandlerAppender);
((Logger)LogManager.getRootLogger()).addAppender(this.streamHandlerAppender);
environment = env;
thread = new Thread(this, "SSHD ConsoleShell " + env.getEnv().get(Environment.ENV_USER));
this.environment = env;
this.Username = env.getEnv().get(Environment.ENV_USER);
this.SshdCommandSender = new SSHDCommandSender();
this.SshdCommandSender.console = this;
thread = new Thread(this, "SSHD ConsoleShell " + this.Username);
thread.start();
} catch (Exception e) {
}
catch (Exception e)
{
throw new IOException("Error starting shell", e);
}
}
public void destroy() {
((Logger) LogManager.getRootLogger()).removeAppender(streamHandlerAppender);
}
@Override
public void destroy(ChannelSession cs) { ((Logger)LogManager.getRootLogger()).removeAppender(this.streamHandlerAppender); }
public void run() {
try {
if (!SshdPlugin.instance.getConfig().getString("mode").equals("RPC"))
printPreamble(consoleReader);
while (true) {
String command = consoleReader.readLine("\r>", null);
if (command == null) continue;
if (command.equals("exit") || command.equals("quit")) break;
Bukkit.getScheduler().runTask(SshdPlugin.instance, () -> {
if (SshdPlugin.instance.getConfig().getString("mode").equals("RPC") &&
command.startsWith("rpc")) {
//NO ECHO NO PREAMBLE AND SHIT
public void run()
{
try
{
// 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);
while (true)
{
String command = this.ConsoleReader.readLine("\r> ", null);
// The user sent CTRL+D to close the shell, terminate the session.
if (command == null)
break;
// Skip someone spamming enter
if (command.trim().isEmpty())
continue;
// User wants to exit
if (command.equals("exit") || command.equals("quit"))
break;
// Clear the text from the screen (on supported terminals)
if (command.equals("cls"))
{
this.ConsoleReader.clearScreen();
this.ConsoleReader.drawLine();
this.ConsoleReader.flush();
continue;
}
// Hide the mkpasswd command input from other users.
Boolean mkpasswd = command.split(" ")[0].equals("mkpasswd");
MinecraftExecutor.submit(() ->
{
if (SshdPlugin.GetInstance().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);
CmdManager.process(this.SshdCommandSender, cmd);
}
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)
{
SshdPlugin.GetInstance().logger.info("<" + this.Username + "> " + command);
CmdManager.process(Sponge.getServer().getConsole(), command);
}
else
{
CmdManager.process(this.SshdCommandSender, command);
}
}
});
}
} catch (IOException e) {
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
} finally {
}
catch (IOException e)
{
SshdPlugin.GetInstance().logger.error("Error processing command from SSH", e);
}
finally
{
SshdPlugin.GetInstance().logger.info(this.Username + " disconnected from SSH.");
callback.onExit(0);
}
}
private void printPreamble(ConsoleReader consoleReader) throws IOException {
consoleReader.println(" _____ _____ _ _ _____" + "\r");
consoleReader.println(" / ____/ ____| | | | __ \\" + "\r");
consoleReader.println("| (___| (___ | |__| | | | |" + "\r");
consoleReader.println(" \\___ \\\\___ \\| __ | | | |" + "\r");
consoleReader.println(" ____) |___) | | | | |__| |" + "\r");
consoleReader.println("|_____/_____/|_| |_|_____/" + "\r");
consoleReader.println("Connected to: " + Bukkit.getServer().getName() + "\r");
consoleReader.println("- " + Bukkit.getServer().getMotd() + "\r");
consoleReader.println("\r");
consoleReader.println("Type 'exit' to exit the shell." + "\r");
consoleReader.println("===============================================" + "\r");
private void printPreamble(ConsoleReader cr) throws IOException
{
File f = new File(SshdPlugin.GetInstance().ConfigDir.toFile(), "motd.txt");
try
{
BufferedReader br = new BufferedReader(new FileReader(f));
String st;
while ((st = br.readLine()) != null)
cr.println(ConsoleLogFormatter.ColorizeString(st) + "\r");
}
catch (FileNotFoundException e)
{
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.
}
// Doesn't really guarantee our actual system hostname but
// it's better than not having one at all.
Platform p = Sponge.getPlatform();
MinecraftVersion mv = p.getMinecraftVersion();
PluginContainer pc = p.getContainer(Platform.Component.API);
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("Type 'exit' or CTRL+D to exit the shell." + "\r");
cr.println("===============================================" + "\r");
}
}
}

View File

@@ -0,0 +1,160 @@
package com.ryanmichela.sshd;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import com.ryanmichela.sshd.BCrypt;
import java.math.BigInteger;
// You should run `openssl speed` to see which parts of these algorithms may need
// tweaking in the future as CPUs and GPUs get faster to crack these hashing algos.
class Cryptography
{
///////////////////////////////////////////////////////////////////////////////
// BCrypt-based password hashing algorithm
///////////////////////////////////////////////////////////////////////////////
public static String BCrypt_HashPassword(String password) throws NoSuchAlgorithmException
{
// This algo handles the salt itself.
return BCrypt.hashpw(password, BCrypt.gensalt());
}
public static Boolean BCrypt_ValidatePassword(String password, String ConfigPassword) throws NoSuchAlgorithmException
{
// Unfortunately, the BCrypt library uses String.compareTo which is not
// hardened against timing attacks so we have to compare the password
// ourselves otherwise it doesn't work well.
String test = BCrypt.hashpw(password, ConfigPassword);
return TimingSafeCmp(test.getBytes(), ConfigPassword.getBytes());
}
///////////////////////////////////////////////////////////////////////////////
// SHA256-based password hashing algorithm
///////////////////////////////////////////////////////////////////////////////
public static String SHA256_HashPassword(String password) throws NoSuchAlgorithmException
{
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] salt = GetSalt();
int iterations = 500000; // sha256 is a fast algo to make lots of hashes for,
// try and make it kinda computationally expensive.
md.update(salt);
byte[] bytes = md.digest(password.getBytes());
// Hash it a few thousand times.
for (int i = 0; i < iterations; i++)
bytes = md.digest(bytes);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++)
sb.append(Integer.toString((bytes[i] & 0xFF) + 0x100, 16).substring(1));
return iterations + "$" + ToHex(salt) + "$" + sb.toString();
}
public static Boolean SHA256_ValidatePassword(String password, String ConfigPassword) throws NoSuchAlgorithmException
{
String[] hparts = ConfigPassword.split("\\$");
int iterations = Integer.parseInt(hparts[0]);
byte[] salt = FromHex(hparts[1]);
String hash = hparts[2];
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(salt);
byte[] bytes = md.digest(password.getBytes());
// Hash it a few thousand times.
for (int i = 0; i < iterations; i++)
bytes = md.digest(bytes);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++)
sb.append(Integer.toString((bytes[i] & 0xFF) + 0x100, 16).substring(1));
return TimingSafeCmp(hash.getBytes(), sb.toString().getBytes());
}
///////////////////////////////////////////////////////////////////////////////
// PBKDF2-based password hashing algoritm
///////////////////////////////////////////////////////////////////////////////
public static String PBKDF2_HashPassword(String password) throws NoSuchAlgorithmException, InvalidKeySpecException
{
char[] passwdchars = password.toCharArray();
int iterations = 20000; // NOTE: Change this as CPUs get faster
// First: Start getting 16 bytes of guaranteed random data to use for our salt
byte[] salt = GetSalt();
PBEKeySpec spec = new PBEKeySpec(passwdchars, salt, iterations, 64*8);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] hash = skf.generateSecret(spec).getEncoded();
return iterations + "$" + ToHex(salt) + "$" + ToHex(hash);
}
public static Boolean PBKDF2_ValidateHash(String password, String ConfigPassword) throws NoSuchAlgorithmException, InvalidKeySpecException
{
String[] hparts = ConfigPassword.split("\\$");
int iterations = Integer.parseInt(hparts[0]);
byte[] salt = FromHex(hparts[1]);
byte[] hash = FromHex(hparts[2]);
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, hash.length * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] cmphash = skf.generateSecret(spec).getEncoded();
return TimingSafeCmp(cmphash, hash);
}
///////////////////////////////////////////////////////////////////////////////
// Utility Functions
///////////////////////////////////////////////////////////////////////////////
public static byte[] GetSalt() throws NoSuchAlgorithmException
{
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[16];
sr.nextBytes(salt);
return salt;
}
// This is a string comparitor function safe against timing attacks.
public static boolean TimingSafeCmp(byte[] str1, byte[] str2)
{
int diff = str1.length ^ str2.length;
for (int i = 0; i < str1.length && i < str2.length; i++)
diff |= str1[i] ^ str2[i];
return diff == 0;
}
private static byte[] FromHex(String hex) throws NoSuchAlgorithmException
{
byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++)
{
bytes[i] = (byte)Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
return bytes;
}
private static String ToHex(byte[] array) throws NoSuchAlgorithmException
{
BigInteger bi = new BigInteger(1, array);
String hex = bi.toString(16);
int paddingLength = (array.length * 2) - hex.length();
if (paddingLength > 0)
return String.format("%0" + paddingLength + "d", 0) + hex;
else
return hex;
}
}

View File

@@ -1,49 +1,59 @@
package com.ryanmichela.sshd;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.exception.SshChannelClosedException;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
/**
* Copyright 2013 Ryan Michela
*/
public class FlushyOutputStream extends OutputStream {
public class FlushyOutputStream extends OutputStream
{
private OutputStream base;
private boolean isClosed = false;
public FlushyOutputStream(OutputStream base) {
public FlushyOutputStream(OutputStream base)
{
this.base = base;
}
@Override
public void write(int b) throws IOException {
if (isClosed) return;
base.write(b);
base.flush();
public void write(int b) throws IOException
{
this.write(BigInteger.valueOf(b).toByteArray());
}
@Override
public void write(byte[] b) throws IOException {
if (isClosed) return;
base.write(b);
base.flush();
public void write(byte[] b) throws IOException
{
this.write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (isClosed) return;
try {
public void write(byte[] b, int off, int len) throws IOException
{
if (isClosed)
return;
try
{
base.write(b, off, len);
base.flush();
} catch (SshException e) {
if (!e.getMessage().contains("channel already closed")) throw e;
}
catch (SshChannelClosedException e)
{
// ignored.
}
}
@Override
public void close() {
public void close() throws IOException
{
isClosed = true;
base.close();
}
}

View File

@@ -1,49 +0,0 @@
package com.ryanmichela.sshd;
import jline.console.ConsoleReader;
import org.apache.sshd.common.SshException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.*;
/**
* Copyright 2013 Ryan Michela
*/
public class FlushyStreamHandler extends StreamHandler {
private ConsoleReader reader;
public FlushyStreamHandler(OutputStream out, Formatter formatter, ConsoleReader reader) {
super(out, formatter);
this.reader = reader;
setLevel(Level.INFO);
}
@Override
public synchronized void publish(LogRecord record) {
record.setMessage(record.getMessage().replace("\n", "\n\r"));
super.publish(record);
flush();
}
@Override
public synchronized void flush() {
try {
reader.print(ConsoleReader.RESET_LINE + "");
reader.flush();
super.flush();
try {
reader.drawLine();
} catch (Throwable ex) {
reader.getCursorBuffer().clear();
}
reader.flush();
super.flush();
} catch (SshException ex) {
// do nothing
} catch (IOException ex) {
Logger.getLogger(FlushyStreamHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
}

View File

@@ -0,0 +1,121 @@
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 com.ryanmichela.sshd.Cryptography;
import com.ryanmichela.sshd.SshdPlugin;
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
{
private static CommandSpec cmdspec;
public static void BuildCommand()
{
CommandSpec pbkdf2 = CommandSpec.builder()
.description(Text.of("PBKDF2 hashed password"))
.permission("sshd.mkpasswd.pbkdf2")
.arguments(GenericArguments.remainingJoinedStrings(Text.of("password")))
.executor((CommandSource source, CommandContext args) -> {
try
{
source.sendMessage(Text.of("\u00A79Your Hash: " + Cryptography.PBKDF2_HashPassword(args.<String>getOne("password").get())));
}
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
public CommandResult execute(CommandSource src, CommandContext args) throws CommandException
{
// This command doesn't do anything.
src.sendMessage(Text.of("\u00A7a/mkpasswd <hash> <password...>\u00A7r"));
src.sendMessage(Text.of("\u00A79Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN\u00A7r"));
return CommandResult.success();
}
}

View File

@@ -1,98 +0,0 @@
package com.ryanmichela.sshd;
import org.apache.commons.codec.binary.Base64;
import java.io.Reader;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
/**
* Copyright 2013 Ryan Michela
*/
public class PemDecoder extends java.io.BufferedReader {
private static final String BEGIN = "^-+\\s*BEGIN.+";
private static final String END = "^-+\\s*END.+";
private static final String COMMENT = "Comment:";
public PemDecoder(Reader in) {
super(in);
}
public PublicKey getPemBytes() throws Exception {
StringBuilder b64 = new StringBuilder();
String line = readLine();
if (!line.matches(BEGIN)) {
return null;
}
for (line = readLine(); line != null; line = readLine()) {
if (!line.matches(END) && !line.startsWith(COMMENT)) {
b64.append(line.trim());
}
}
return decodePublicKey(b64.toString());
}
private byte[] bytes;
private int pos;
private PublicKey decodePublicKey(String keyLine) throws Exception {
bytes = null;
pos = 0;
// look for the Base64 encoded part of the line to decode
// both ssh-rsa and ssh-dss begin with "AAAA" due to the length bytes
for (String part : keyLine.split(" ")) {
if (part.startsWith("AAAA")) {
bytes = Base64.decodeBase64(part.getBytes());
break;
}
}
if (bytes == null) {
throw new IllegalArgumentException("no Base64 part to decode");
}
String type = decodeType();
if (type.equals("ssh-rsa")) {
BigInteger e = decodeBigInt();
BigInteger m = decodeBigInt();
RSAPublicKeySpec spec = new RSAPublicKeySpec(m, e);
return KeyFactory.getInstance("RSA").generatePublic(spec);
} else if (type.equals("ssh-dss")) {
BigInteger p = decodeBigInt();
BigInteger q = decodeBigInt();
BigInteger g = decodeBigInt();
BigInteger y = decodeBigInt();
DSAPublicKeySpec spec = new DSAPublicKeySpec(y, p, q, g);
return KeyFactory.getInstance("DSA").generatePublic(spec);
} else {
throw new IllegalArgumentException("unknown type " + type);
}
}
private String decodeType() {
int len = decodeInt();
String type = new String(bytes, pos, len);
pos += len;
return type;
}
private int decodeInt() {
return ((bytes[pos++] & 0xFF) << 24) | ((bytes[pos++] & 0xFF) << 16)
| ((bytes[pos++] & 0xFF) << 8) | (bytes[pos++] & 0xFF);
}
private BigInteger decodeBigInt() {
int len = decodeInt();
byte[] bigIntBytes = new byte[len];
System.arraycopy(bytes, pos, bigIntBytes, 0, len);
pos += len;
return new BigInteger(bigIntBytes);
}
}

View File

@@ -1,52 +1,80 @@
package com.ryanmichela.sshd;
import org.apache.commons.lang.ArrayUtils;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import java.io.File;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.io.FileReader;
import java.security.PublicKey;
/**
* Copyright 2013 Ryan Michela
*/
public class PublicKeyAuthenticator implements PublickeyAuthenticator {
public class PublicKeyAuthenticator implements PublickeyAuthenticator
{
private File authorizedKeysDir;
private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
public PublicKeyAuthenticator(File authorizedKeysDir) {
this.authorizedKeysDir = authorizedKeysDir;
}
public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
@Override
public boolean authenticate(String username, PublicKey key, ServerSession session) {
@Override public boolean authenticate(String username, PublicKey key, ServerSession session)
{
byte[] keyBytes = key.getEncoded();
File keyFile = new File(authorizedKeysDir, username);
Integer tries = SshdPlugin.GetInstance().LoginRetries;
if (keyFile.exists()) {
try {
if (keyFile.exists())
{
try
{
// Read all the public key entries
List<AuthorizedKeyEntry> pklist = AuthorizedKeyEntry.readAuthorizedKeys(keyFile.toPath());
// Get an authenticator
PublickeyAuthenticator auth = PublickeyAuthenticator.fromAuthorizedEntries(username, session, pklist,
PublicKeyEntryResolver.IGNORING);
FileReader fr = new FileReader(keyFile);
PemDecoder pd = new PemDecoder(fr);
PublicKey k = pd.getPemBytes();
pd.close();
if (k != null) {
if (ArrayUtils.isEquals(key.getEncoded(), k.getEncoded())) {
// Validate that the logging in user has the same valid SSH key
if (auth.authenticate(username, key, session))
{
FailCounts.put(username, 0);
return true;
}
} else {
SshdPlugin.instance.getLogger().severe("Failed to parse PEM file. " + keyFile.getAbsolutePath());
else
{
SshdPlugin.GetInstance().logger.info(
username + " failed authentication via SSH session using key file " + keyFile.getAbsolutePath());
}
} catch (Exception e) {
SshdPlugin.instance.getLogger()
.severe("Failed to process public key " + keyFile.getAbsolutePath() + ". " + e.getMessage());
// If the user fails with several SSH keys, then terminate the connection.
if (this.FailCounts.containsKey(username))
this.FailCounts.put(username, this.FailCounts.get(username) + 1);
else
this.FailCounts.put(username, 1);
if (this.FailCounts.get(username) >= tries)
{
this.FailCounts.put(username, 0);
SshdPlugin.GetInstance().logger.info("Too many failures for " + username + ", disconnecting.");
session.close(true);
}
} else {
SshdPlugin.instance.getLogger().warning("Could not locate public key for " + username +
". Make sure the user's key is named the same as their user name " +
"without a file extension.");
return false;
}
catch (Exception e)
{
SshdPlugin.GetInstance().logger.error("Failed to process public key " + keyFile.getAbsolutePath() + " " + e.getMessage());
}
}
else
{
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 "
+ "without a file extension.");
}
return false;

View File

@@ -1,96 +0,0 @@
package com.ryanmichela.sshd;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Copyright 2013 Ryan Michela
*/
public class ReflectionUtil {
public static void setProtectedValue(Object o, String field, Object newValue) {
setProtectedValue(o.getClass(), o, field, newValue);
}
public static void setProtectedValue(Class c, String field, Object newValue) {
setProtectedValue(c, null, field, newValue);
}
public static void setProtectedValue(Class c, Object o, String field, Object newValue) {
try {
Field f = c.getDeclaredField(field);
f.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.set(o, newValue);
} catch (NoSuchFieldException | IllegalAccessException ex) {
System.out.println("*** " + c.getName() + ":" + ex);
}
}
public static <T> T getProtectedValue(Object obj, String fieldName) {
try {
Class c = obj.getClass();
while (c != Object.class) {
Field[] fields = c.getDeclaredFields();
for (Field f : fields) {
if (f.getName() == fieldName) {
f.setAccessible(true);
return (T) f.get(obj);
}
}
c = c.getSuperclass();
}
System.out.println("*** " + obj.getClass().getName() + ":No such field");
return null;
} catch (Exception ex) {
System.out.println("*** " + obj.getClass().getName() + ":" + ex);
return null;
}
}
public static <T> T getProtectedValue(Class c, String field) {
try {
Field f = c.getDeclaredField(field);
f.setAccessible(true);
return (T) f.get(c);
} catch (Exception ex) {
System.out.println("*** " + c.getName() + ":" + ex);
return null;
}
}
public static Object invokeProtectedMethod(Class c, String method, Object... args) {
return invokeProtectedMethod(c, null, method, args);
}
public static Object invokeProtectedMethod(Object o, String method, Object... args) {
return invokeProtectedMethod(o.getClass(), o, method, args);
}
public static Object invokeProtectedMethod(Class c, Object o, String method, Object... args) {
try {
Class[] pTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Integer) {
pTypes[i] = int.class;
} else {
pTypes[i] = args[i].getClass();
}
}
Method m = c.getDeclaredMethod(method, pTypes);
m.setAccessible(true);
return m.invoke(o, args);
} catch (Exception ex) {
System.out.println("*** " + c.getName() + "." + method + "(): " + ex);
return null;
}
}
}

View File

@@ -1,79 +1,143 @@
package com.ryanmichela.sshd;
import com.ryanmichela.sshd.utils.Config;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.spec.CommandSpec;
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.server.SshServer;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import org.bukkit.plugin.java.JavaPlugin;
import com.ryanmichela.sshd.ConsoleShellFactory;
import com.ryanmichela.sshd.MkpasswdCommand;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.logging.Level;
/**
* Copyright 2013 Ryan Michela
*/
public class SshdPlugin extends JavaPlugin {
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.spongepowered.api.text.Text;
@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
{
String ListenAddress = "";
Integer Port = 1025;
String Mode = "";
Boolean EnableSFTP = true;
Integer LoginRetries = 3;
String PasswordType = "";
// Credentials
private SshServer sshd;
public static SshdPlugin instance;
private static SshdPlugin instance;
@Override
public void onLoad() {
saveDefaultConfig();
File authorizedKeys = new File(getDataFolder(), "authorized_keys");
if (!authorizedKeys.exists()) {
authorizedKeys.mkdirs();
}
@Inject
public Logger logger;
// 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);
}
@Inject
@DefaultConfig(sharedRoot = false)
public Path DefaultConfig;
@Override
public void onEnable() {
@Inject
@ConfigDir(sharedRoot = false)
public Path ConfigDir;
public Config config;
@Listener
public void onServerStart(GameStartedServerEvent event)
{
instance = this;
// Parse our config
config = new Config();
config.setup();
// Make sure our authorized_keys folder exists
File authorizedKeys = new File(this.ConfigDir.toFile(), "authorized_keys");
if (!authorizedKeys.exists())
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
{
File motd = new File(this.ConfigDir.toFile(), "motd.txt");
if (!motd.exists())
{
InputStream link = (getClass().getResourceAsStream("/motd.txt"));
Files.copy(link, motd.getAbsoluteFile().toPath());
}
}
catch (IOException e)
{
e.printStackTrace();
}
sshd = SshServer.setUpDefaultServer();
sshd.setPort(getConfig().getInt("port", 22));
String host = getConfig().getString("listenAddress", "all");
sshd.setHost(host.equals("all") ? null : host);
sshd.setPort(this.Port);
sshd.setHost(this.ListenAddress.equals("all") ? null : this.ListenAddress);
File hostKey = new File(getDataFolder(), "hostkey");
File authorizedKeys = new File(getDataFolder(), "authorized_keys");
File hostKey = new File(this.ConfigDir.toFile(), "hostkey");
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey));
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey.toPath()));
sshd.setShellFactory(new ConsoleShellFactory());
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
if (getConfig().getBoolean("enableSFTP")) {
if (this.EnableSFTP)
{
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
sshd.setFileSystemFactory(new VirtualFileSystemFactory(
FileSystems.getDefault().getPath(
getDataFolder().getAbsolutePath()
).getParent().getParent()
));
sshd.setFileSystemFactory(new VirtualFileSystemFactory(this.ConfigDir.getParent().getParent()));
}
MkpasswdCommand.BuildCommand();
sshd.setCommandFactory(new ConsoleCommandFactory());
try {
try
{
sshd.start();
} catch (IOException e) {
getLogger().log(Level.SEVERE, "Failed to start SSH server! ", e);
}
catch (IOException e)
{
logger.error("Failed to start SSH server! ", e);
}
@Override
public void onDisable() {
try {
sshd.stop();
} catch (Exception e) {
// do nothing
logger.info("Loaded Minecraft-SSHD.");
}
public static SshdPlugin GetInstance()
{
return instance;
}
public Logger GetLogger()
{
return this.logger;
}
}

View File

@@ -1,48 +1,73 @@
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.util.logging.LogRecord;
import java.util.logging.StreamHandler;
import java.nio.charset.Charset;
/**
* Copyright 2014 Ryan Michela
*/
public class StreamHandlerAppender implements Appender {
private StreamHandler streamHandler;
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(StreamHandler streamHandler) {
this.streamHandler = streamHandler;
public StreamHandlerAppender(ConsoleReader console)
{
this.console = console;
uuid = UUID.randomUUID();
}
@Override
public void append(LogEvent logEvent) {
java.util.logging.Level level;
public void append(LogEvent logEvent)
{
if (logEvent.getLevel() == Level.DEBUG || logEvent.getLevel() == Level.TRACE)
return;
if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.DEBUG)) {
level = java.util.logging.Level.FINE;
} else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.INFO)) {
level = java.util.logging.Level.INFO;
} else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.WARN)) {
level = java.util.logging.Level.WARNING;
} else if (logEvent.getLevel().equals(org.apache.logging.log4j.Level.ERROR)) {
level = java.util.logging.Level.SEVERE;
} else {
level = java.util.logging.Level.INFO;
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();
}
String message = logEvent.getMessage().getFormattedMessage();
LogRecord logRecord = new LogRecord(level, message);
streamHandler.publish(logRecord);
}
@Override

View File

@@ -1,130 +1,178 @@
package com.ryanmichela.sshd.implementations;
import com.ryanmichela.sshd.ConsoleShellFactory;
import com.ryanmichela.sshd.SshdPlugin;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationAbandonedEvent;
import org.bukkit.conversations.ManuallyAbandonedConversationCanceller;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
import com.ryanmichela.sshd.ConsoleShellFactory;
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.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.Optional;
import java.util.List;
public class SSHDCommandSender implements ConsoleCommandSender, 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;
private UUID uuid = UUID.randomUUID();
private final PermissibleBase perm = new PermissibleBase(this);
private final SSHDConversationTracker conversationTracker = new SSHDConversationTracker();
// This is an override for Sponge to work with the SSH consoles.
@Override
public void sendMessage(Text message)
{
this.sendRawMessage(message.toPlain());
}
public void sendMessage(String message) {
// Back port from Spigot/BungeeCord-style API calls.
public void sendMessage(String message)
{
this.sendRawMessage(message);
}
public void sendRawMessage(String message) {
if(ConsoleShellFactory.ConsoleShell.consoleReader == null) return;
try {
ConsoleShellFactory.ConsoleShell.consoleReader.println(ChatColor.stripColor(message));
} catch (IOException e) {
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e);
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)
return;
try
{
this.console.ConsoleReader.print(ConsoleReader.BACKSPACE + "" + ConsoleReader.BACKSPACE);
this.console.ConsoleReader.println(ConsoleLogFormatter.ColorizeString(message).replaceAll("\n", "\n\r") + "\r");
this.console.ConsoleReader.print(ConsoleReader.RESET_LINE + "");
this.console.ConsoleReader.flush();
try
{
this.console.ConsoleReader.drawLine();
}
catch (Throwable ex)
{
this.console.ConsoleReader.getCursorBuffer().clear();
}
this.console.ConsoleReader.flush();
}
catch (IOException e)
{
SshdPlugin.GetInstance().logger.error("Error sending message to SSHDCommandSender", e);
}
}
public void sendMessage(String[] messages) {
public void sendMessage(String[] messages)
{
Arrays.asList(messages).forEach(this::sendMessage);
}
public MessageChannel getMessageChannel()
{
return mc;
}
public void setMessageChannel(MessageChannel channel)
{
mc = channel;
}
public String getName() {
return "SSHD CONSOLE";
return "SSHD Console";
}
public boolean isOp() {
return true;
@Override
public String getIdentifier() {
return uuid.toString();
}
public void setOp(boolean value) {
throw new UnsupportedOperationException("Cannot change operator status of server console");
@Override
public Set<Context> getActiveContexts()
{
// 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;
}
public boolean beginConversation(Conversation conversation) {
return this.conversationTracker.beginConversation(conversation);
}
public void abandonConversation(Conversation conversation) {
this.conversationTracker.abandonConversation(conversation, new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller()));
}
public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
this.conversationTracker.abandonConversation(conversation, details);
}
public void acceptConversationInput(String input) {
this.conversationTracker.acceptConversationInput(input);
}
public boolean isConversing() {
return this.conversationTracker.isConversing();
}
public boolean isPermissionSet(String name) {
return this.perm.isPermissionSet(name);
}
public boolean isPermissionSet(Permission perm) {
return this.perm.isPermissionSet(perm);
}
public boolean hasPermission(String name) {
return this.perm.hasPermission(name);
}
public boolean hasPermission(Permission perm) {
return this.perm.hasPermission(perm);
}
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) {
return this.perm.addAttachment(plugin, name, value);
}
public PermissionAttachment addAttachment(Plugin plugin) {
return this.perm.addAttachment(plugin);
}
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) {
return this.perm.addAttachment(plugin, name, value, ticks);
}
public PermissionAttachment addAttachment(Plugin plugin, int ticks) {
return this.perm.addAttachment(plugin, ticks);
}
public void removeAttachment(PermissionAttachment attachment) {
this.perm.removeAttachment(attachment);
}
public void recalculatePermissions() {
this.perm.recalculatePermissions();
}
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
return this.perm.getEffectivePermissions();
}
public boolean isPlayer() {
@Override
public boolean isSubjectDataPersisted()
{
// TODO Auto-generated method stub
return false;
}
public Server getServer() {
return Bukkit.getServer();
@Override
public boolean isChildOf(Set<Context> contexts, SubjectReference parent)
{
// TODO Auto-generated method stub
return false;
}
@Override
public SubjectData getTransientSubjectData()
{
// TODO Auto-generated method stub
return null;
}
@Override
public SubjectData getSubjectData()
{
// TODO Auto-generated method stub
return null;
}
@Override
public Tristate getPermissionValue(Set<Context> contexts, String permission)
{
// We're allowed to view all permissions.
return Tristate.TRUE;
}
@Override
public List<SubjectReference> getParents(Set<Context> contexts)
{
// 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

@@ -1,78 +0,0 @@
package com.ryanmichela.sshd.implementations;
import org.bukkit.Bukkit;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationAbandonedEvent;
import org.bukkit.conversations.ManuallyAbandonedConversationCanceller;
import java.util.LinkedList;
import java.util.logging.Level;
public class SSHDConversationTracker {
private LinkedList<Conversation> conversationQueue = new LinkedList<>();
synchronized boolean beginConversation(Conversation conversation) {
if (!this.conversationQueue.contains(conversation)) {
this.conversationQueue.addLast(conversation);
if (this.conversationQueue.getFirst() == conversation) {
conversation.begin();
conversation.outputNextPrompt();
return true;
}
}
return true;
}
synchronized void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
if (!this.conversationQueue.isEmpty()) {
if (this.conversationQueue.getFirst() == conversation) {
conversation.abandon(details);
}
if (this.conversationQueue.contains(conversation)) {
this.conversationQueue.remove(conversation);
}
if (!this.conversationQueue.isEmpty()) {
this.conversationQueue.getFirst().outputNextPrompt();
}
}
}
public synchronized void abandonAllConversations() {
LinkedList<Conversation> oldQueue = this.conversationQueue;
this.conversationQueue = new LinkedList<>();
for (Conversation conversation : oldQueue) {
try {
conversation.abandon(new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller()));
} catch (Throwable var5) {
Bukkit.getLogger().log(Level.SEVERE, "Unexpected exception while abandoning a conversation", var5);
}
}
}
synchronized void acceptConversationInput(String input) {
if (this.isConversing()) {
Conversation conversation = this.conversationQueue.getFirst();
try {
conversation.acceptInput(input);
} 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);
}
}
}
synchronized boolean isConversing() {
return !this.conversationQueue.isEmpty();
}
public synchronized boolean isConversingModaly() {
return this.isConversing() && this.conversationQueue.getFirst().isModal();
}
}

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,22 +1,41 @@
# The IP addresses(s) the SSH server will listen on. Use a comma separated list for multiple addresses.
# Leave as "all" for all addresses.
listenAddress: all
# The port the SSH server will listen on.
port: 22
ListenAddress: all
# The port the SSH server will listen on. Note that anything above 1024 will require you to run
# 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
mode: DEFAULT
Mode: DEFAULT
# 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.
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.
# To authorize a user to log in with public key authentication, install their public
# PEM certificate in the authorized_users directory. Name the key file with user's user
# name (no file extension).
# To authorize a user to login with their public key, install their key using the
# OpenSSH authorized_keys file format in the authorized_users directory. Name the key
# 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.
credentials:
# Type of hashing to use for the passwords below.
# Options are: PLAIN (insecure), bcrypt, pbkdf2, sha256
#
# You can use the console/in-game command `/mkpasswd [hash] PASSWORD` to
# generate a password hash string then copy it for your passwords below.
# You can also use `/mkpasswd help` to see what algorithms are supported.
PasswordType: bcrypt
# Associate each username with a password hash (or the password if the PasswordType is set to PLAIN)
Credentials:
# user1: password1
# user2: password2

View File

@@ -0,0 +1,8 @@
§l§4 _____ _____ _ _ _____§r
§l§6 / ____/ ____| | | | __ \§r
§l§2| (___| (___ | |__| | | | |§r
§l§3 \___ \\___ \| __ | | | |§r
§l§9 ____) |___) | | | | |__| |§r
§l§5|_____/_____/|_| |_|_____/§r
===============================================

View File

@@ -1,4 +0,0 @@
name: SSHD
version: ${project.version}
author: Ryan Michela, Haarolean, toxuin
main: com.ryanmichela.sshd.SshdPlugin