Compare commits
45 Commits
feature/wh
...
master
Author | SHA1 | Date | |
---|---|---|---|
21364a3f63 | |||
a0cafa3e62 | |||
|
d348e26b49 | ||
|
e0080fb1a0 | ||
|
40f63bdeeb | ||
|
5a247576c4 | ||
|
ae8d5bff1d | ||
|
3ad2a810f8 | ||
|
de9d723416 | ||
|
1c93b9fc0e | ||
|
170a96eb94 | ||
|
ec1ed8cb9e | ||
|
67146ce589 | ||
|
e986cacc50 | ||
|
0afba39d57 | ||
|
1b4c7c2304 | ||
|
4a39525809 | ||
|
73c30f5214 | ||
|
3e45f7ebf4 | ||
|
0e05bb61bc | ||
|
0635ea7a35 | ||
|
25287b1580 | ||
|
0458179597 | ||
|
dc76da9ac1 | ||
|
7330b1eead | ||
|
8f6319d979 | ||
|
272e7cf8dc | ||
|
69d31175d4 | ||
|
16f5c063ce | ||
|
2f1e03d7a2 | ||
|
32f65a506e | ||
|
703c69b055 | ||
|
15faff3dd4 | ||
|
dc8e49f643 | ||
|
2380c42089 | ||
|
02b700b07f | ||
|
87cb4d8929 | ||
|
ac6a552e95 | ||
|
0951c6f971 | ||
|
5a9a5245b9 | ||
|
396351771c | ||
|
f034cef465 | ||
|
956f0cc3ef | ||
|
027605e743 | ||
|
be9c53bf6f |
91
Minecraft-SSHD-Bukkit/pom.xml
Normal file
91
Minecraft-SSHD-Bukkit/pom.xml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.ryanmichela</groupId>
|
||||||
|
<artifactId>sshd</artifactId>
|
||||||
|
<version>2.1.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>Minecraft-SSHD-Bukkit</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<!-- License: This module extends bukkit which is GPL v2 -->
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>GPL2</name>
|
||||||
|
<url>http://www.gnu.org/licenses/gpl-2.0.html</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.spigotmc</groupId>
|
||||||
|
<artifactId>spigot-api</artifactId>
|
||||||
|
<version>1.19.4-R0.1-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Build -->
|
||||||
|
<build>
|
||||||
|
<defaultGoal>clean package</defaultGoal>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>3.1.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<finalName>${project.name}-${project.version}</finalName>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Compile plugin -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.7.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
<showDeprecation>true</showDeprecation>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,791 @@
|
|||||||
|
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 />
|
||||||
|
* System.out.println("It matches");<br />
|
||||||
|
* else<br />
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2013 Ryan Michela
|
||||||
|
*/
|
||||||
|
public class ConfigPasswordAuthenticator implements PasswordAuthenticator
|
||||||
|
{
|
||||||
|
private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean authenticate(String username, String password, ServerSession ss)
|
||||||
|
{
|
||||||
|
// Depending on our hash type, we have to try and figure out what we're doing.
|
||||||
|
String HashType = SshdPlugin.instance.getConfig().getString("PasswordType");
|
||||||
|
String ConfigHash = SshdPlugin.instance.getConfig().getString("Credentials." + username.trim() + ".password");
|
||||||
|
|
||||||
|
if (ConfigHash == null)
|
||||||
|
SshdPlugin.instance.getLogger().warning("Config has no such user: " + username);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (HashType.equalsIgnoreCase("PLAIN"))
|
||||||
|
{
|
||||||
|
if (ConfigHash.equals(password))
|
||||||
|
{
|
||||||
|
FailCounts.put(username, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using " + HashType + "-based password authentication.");
|
||||||
|
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries", 3);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(3000);
|
||||||
|
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.instance.getLogger().info("Too many failures for " + username + ", disconnecting.");
|
||||||
|
ss.close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package com.ryanmichela.sshd;
|
||||||
|
|
||||||
|
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 java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2013 Ryan Michela
|
||||||
|
*/
|
||||||
|
public class ConsoleCommandFactory implements CommandFactory
|
||||||
|
{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Command createCommand(ChannelSession cs, String command)
|
||||||
|
{
|
||||||
|
return new ConsoleCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConsoleCommand implements Command
|
||||||
|
{
|
||||||
|
private String command;
|
||||||
|
private InputStream in;
|
||||||
|
private OutputStream out;
|
||||||
|
private OutputStream err;
|
||||||
|
private ExitCallback callback;
|
||||||
|
|
||||||
|
public ConsoleCommand(String command)
|
||||||
|
{
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputStream(InputStream in)
|
||||||
|
{
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputStream(OutputStream out)
|
||||||
|
{
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorStream(OutputStream err)
|
||||||
|
{
|
||||||
|
this.err = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExitCallback(ExitCallback callback)
|
||||||
|
{
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(ChannelSession cs, Environment environment) throws IOException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SshdPlugin.instance.getLogger()
|
||||||
|
.info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command);
|
||||||
|
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
SshdPlugin.instance.getLogger().severe("Error processing command from SSH -" + e.getMessage());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
callback.onExit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy(ChannelSession cn) {}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package com.ryanmichela.sshd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2013 Ryan Michela
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.bukkit.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 {
|
||||||
|
|
||||||
|
private SimpleDateFormat dateFormat;
|
||||||
|
private static final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
|
||||||
|
|
||||||
|
public ConsoleLogFormatter() {
|
||||||
|
this.dateFormat = new SimpleDateFormat("HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String ColorizeString(String str)
|
||||||
|
{
|
||||||
|
// ORIGINAL CODE FROM org.bukkit.craftbukkit.command.ColouredConsoleSender
|
||||||
|
|
||||||
|
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.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.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());
|
||||||
|
replacements.put(ChatColor.BOLD, Ansi.ansi().a(Ansi.Attribute.UNDERLINE_DOUBLE).toString());
|
||||||
|
replacements.put(ChatColor.STRIKETHROUGH, Ansi.ansi().a(Ansi.Attribute.STRIKETHROUGH_ON).toString());
|
||||||
|
replacements.put(ChatColor.UNDERLINE, Ansi.ansi().a(Ansi.Attribute.UNDERLINE).toString());
|
||||||
|
replacements.put(ChatColor.ITALIC, Ansi.ansi().a(Ansi.Attribute.ITALIC).toString());
|
||||||
|
replacements.put(ChatColor.RESET, Ansi.ansi().a(Ansi.Attribute.RESET).toString());
|
||||||
|
|
||||||
|
String result = str;
|
||||||
|
for (ChatColor color : ChatColor.values())
|
||||||
|
{
|
||||||
|
if (replacements.containsKey(color))
|
||||||
|
{
|
||||||
|
result = result.replaceAll("(?i)" + color.toString(), replacements.get(color));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result.replaceAll("(?i)" + color.toString(), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += Ansi.ansi().reset().toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String format(LogRecord logrecord)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class.forName("org.bukkit.craftbukkit.command.ColouredConsoleSender");
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException ignored)
|
||||||
|
{
|
||||||
|
// MEANS WE'RE ON PAPER/TACO/OTHER SHIT
|
||||||
|
colorize(logrecord);
|
||||||
|
}
|
||||||
|
StringBuilder stringbuilder = new StringBuilder();
|
||||||
|
|
||||||
|
stringbuilder.append(" [");
|
||||||
|
stringbuilder.append(this.dateFormat.format(logrecord.getMillis())).append(" ");
|
||||||
|
|
||||||
|
stringbuilder.append(logrecord.getLevel().getName()).append("]: ");
|
||||||
|
stringbuilder.append(this.formatMessage(logrecord));
|
||||||
|
stringbuilder.append('\n');
|
||||||
|
Throwable throwable = logrecord.getThrown();
|
||||||
|
|
||||||
|
if (throwable != null)
|
||||||
|
{
|
||||||
|
StringWriter stringwriter = new StringWriter();
|
||||||
|
|
||||||
|
throwable.printStackTrace(new PrintWriter(stringwriter));
|
||||||
|
stringbuilder.append(stringwriter.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringbuilder.toString().replace("\n", "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void colorize(LogRecord logrecord)
|
||||||
|
{
|
||||||
|
String result = ColorizeString(logrecord.getMessage());
|
||||||
|
logrecord.setMessage(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,161 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +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
|
||||||
|
{
|
||||||
|
|
||||||
|
private OutputStream base;
|
||||||
|
private boolean isClosed = false;
|
||||||
|
|
||||||
|
public FlushyOutputStream(OutputStream base)
|
||||||
|
{
|
||||||
|
this.base = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException
|
||||||
|
{
|
||||||
|
this.write(BigInteger.valueOf(b).toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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
|
||||||
|
{
|
||||||
|
base.write(b, off, len);
|
||||||
|
base.flush();
|
||||||
|
}
|
||||||
|
catch (SshChannelClosedException e)
|
||||||
|
{
|
||||||
|
// ignored.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
isClosed = true;
|
||||||
|
base.close();
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package com.ryanmichela.sshd;
|
package com.ryanmichela.sshd;
|
||||||
|
|
||||||
|
import jline.console.ConsoleReader;
|
||||||
import org.apache.sshd.common.SshException;
|
import org.apache.sshd.common.SshException;
|
||||||
import org.bukkit.craftbukkit.libs.jline.console.ConsoleReader;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@ -10,38 +10,51 @@ import java.util.logging.*;
|
|||||||
/**
|
/**
|
||||||
* Copyright 2013 Ryan Michela
|
* Copyright 2013 Ryan Michela
|
||||||
*/
|
*/
|
||||||
public class FlushyStreamHandler extends StreamHandler {
|
public class FlushyStreamHandler extends StreamHandler
|
||||||
|
{
|
||||||
private ConsoleReader reader;
|
private ConsoleReader reader;
|
||||||
|
|
||||||
public FlushyStreamHandler(OutputStream out, Formatter formatter, ConsoleReader reader) {
|
public FlushyStreamHandler(OutputStream out, Formatter formatter, ConsoleReader reader)
|
||||||
|
{
|
||||||
super(out, formatter);
|
super(out, formatter);
|
||||||
this.reader = reader;
|
this.reader = reader;
|
||||||
setLevel(Level.INFO);
|
setLevel(Level.INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void publish(LogRecord record) {
|
public synchronized void publish(LogRecord record)
|
||||||
|
{
|
||||||
record.setMessage(record.getMessage().replace("\n", "\n\r"));
|
record.setMessage(record.getMessage().replace("\n", "\n\r"));
|
||||||
super.publish(record);
|
super.publish(record);
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void flush() {
|
public synchronized void flush()
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
reader.print(ConsoleReader.RESET_LINE + "");
|
reader.print(ConsoleReader.RESET_LINE + "");
|
||||||
reader.flush();
|
reader.flush();
|
||||||
super.flush();
|
super.flush();
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
reader.drawLine();
|
reader.drawLine();
|
||||||
} catch (Throwable ex) {
|
}
|
||||||
|
catch (Throwable ex)
|
||||||
|
{
|
||||||
reader.getCursorBuffer().clear();
|
reader.getCursorBuffer().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.flush();
|
reader.flush();
|
||||||
super.flush();
|
super.flush();
|
||||||
} catch (SshException ex) {
|
}
|
||||||
|
catch (SshException ex)
|
||||||
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
} catch (IOException ex) {
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
Logger.getLogger(FlushyStreamHandler.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(FlushyStreamHandler.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
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 net.md_5.bungee.api.chat.ClickEvent;
|
||||||
|
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||||
|
import net.md_5.bungee.api.chat.HoverEvent;
|
||||||
|
import net.md_5.bungee.api.chat.TextComponent;
|
||||||
|
|
||||||
|
import com.ryanmichela.sshd.Cryptography;
|
||||||
|
import com.ryanmichela.sshd.SshdPlugin;
|
||||||
|
|
||||||
|
class MkpasswdCommand implements CommandExecutor
|
||||||
|
{
|
||||||
|
// Because Spigot's failed syntax API is really less than ideal (you should be required to add a
|
||||||
|
// SendSyntax function override), we're just always going to return true even for syntax failures
|
||||||
|
// as we will handle the syntax message internally. This also lets us send the messages more
|
||||||
|
// securely to the client without people knowing we're using the command. This prevents password
|
||||||
|
// or hash leakages from the user to other connected users. Plus this syntax will show how
|
||||||
|
// to both use the command and what hashes we support which is important for people who don't
|
||||||
|
// know how to RTFM. - Justin
|
||||||
|
private void SendSyntax(CommandSender sender, boolean invalid)
|
||||||
|
{
|
||||||
|
if (invalid)
|
||||||
|
sender.sendMessage("\u00A7cInvalid Syntax\u00A7r");
|
||||||
|
sender.sendMessage("\u00A7a/mkpasswd <help|hash> <password>\u00A7r");
|
||||||
|
sender.sendMessage("\u00A79Supported Hashes: SHA256, PBKDF2, BCRYPT, PLAIN\u00A7r");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args)
|
||||||
|
{
|
||||||
|
String algoritm, password;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Stupid bukkit, we have to concatenate the arguments together if they're using
|
||||||
|
// spaces in their passwords otherwise it won't be as strong as it should be.
|
||||||
|
algoritm = args[0];
|
||||||
|
password = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
|
||||||
|
if (password.trim().isEmpty()) // Shortcut to the catch statement below.
|
||||||
|
throw new ArrayIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
catch (ArrayIndexOutOfBoundsException e)
|
||||||
|
{
|
||||||
|
// ignore it.
|
||||||
|
this.SendSyntax(sender, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasperm = (sender instanceof Player) ? ((Player)sender).hasPermission("sshd.mkpasswd") : true;
|
||||||
|
|
||||||
|
if (hasperm)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String hash = "";
|
||||||
|
// Dumb but whatever. Some people are really dense.
|
||||||
|
if (algoritm.equalsIgnoreCase("PLAIN"))
|
||||||
|
{
|
||||||
|
// I mean c'mon...
|
||||||
|
sender.sendMessage("\u00A79Your Hash: \u00A7cIt's literally your unhashed password.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (algoritm.equalsIgnoreCase("pbkdf2"))
|
||||||
|
hash = Cryptography.PBKDF2_HashPassword(password);
|
||||||
|
else if (algoritm.equalsIgnoreCase("bcrypt"))
|
||||||
|
hash = Cryptography.BCrypt_HashPassword(password);
|
||||||
|
else if (algoritm.equalsIgnoreCase("sha256"))
|
||||||
|
hash = Cryptography.SHA256_HashPassword(password);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.SendSyntax(sender, !algoritm.equalsIgnoreCase("help"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextComponent msg = new TextComponent("\u00A79Your Hash: " + hash + "\u00A7r");
|
||||||
|
msg.setClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, hash));
|
||||||
|
msg.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Click to copy the hash!").create()));
|
||||||
|
|
||||||
|
sender.spigot().sendMessage(msg);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// We're console, just print the stack trace.
|
||||||
|
e.printStackTrace();
|
||||||
|
sender.sendMessage("\u00A7cAn error occured. Please check console for details.\u00A7r");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sender.sendMessage("\u00A7cPermission Denied.\u00A7r");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.ryanmichela.sshd;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import com.ryanmichela.sshd.SshdPlugin;
|
||||||
|
|
||||||
|
public class PermissionUtil
|
||||||
|
{
|
||||||
|
public static Optional<String> GetCredential(String username, String credential)
|
||||||
|
{
|
||||||
|
String Default = SshdPlugin.instance.getConfig().getString("Credentials.$default." + credential);
|
||||||
|
String cred = SshdPlugin.instance.getConfig().getString("Credentials." + username + "." + credential, Default);
|
||||||
|
|
||||||
|
if (cred == null)
|
||||||
|
return Optional.empty();
|
||||||
|
|
||||||
|
else if (cred.isEmpty())
|
||||||
|
return Optional.empty();
|
||||||
|
|
||||||
|
else
|
||||||
|
return Optional.of(cred);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,83 @@
|
|||||||
|
package com.ryanmichela.sshd;
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
private File authorizedKeysDir;
|
||||||
|
private Map<String, Integer> FailCounts = new HashMap<String, Integer>();
|
||||||
|
|
||||||
|
public PublicKeyAuthenticator(File authorizedKeysDir) { this.authorizedKeysDir = authorizedKeysDir; }
|
||||||
|
|
||||||
|
@Override public boolean authenticate(String username, PublicKey key, ServerSession session)
|
||||||
|
{
|
||||||
|
byte[] keyBytes = key.getEncoded();
|
||||||
|
File keyFile = new File(authorizedKeysDir, username);
|
||||||
|
Integer tries = SshdPlugin.instance.getConfig().getInt("LoginRetries", 3);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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().info(
|
||||||
|
username + " failed authentication via SSH session using key file " + keyFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.instance.getLogger().info("Too many failures for " + username + ", disconnecting.");
|
||||||
|
session.close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SshdPlugin.instance.getLogger().severe("Failed to process public key " + keyFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -8,16 +8,21 @@ import java.lang.reflect.Modifier;
|
|||||||
* Copyright 2013 Ryan Michela
|
* Copyright 2013 Ryan Michela
|
||||||
*/
|
*/
|
||||||
public class ReflectionUtil {
|
public class ReflectionUtil {
|
||||||
public static void setProtectedValue(Object o, String field, Object newValue) {
|
|
||||||
|
public static void setProtectedValue(Object o, String field, Object newValue)
|
||||||
|
{
|
||||||
setProtectedValue(o.getClass(), o, field, newValue);
|
setProtectedValue(o.getClass(), o, field, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setProtectedValue(Class c, String field, Object newValue) {
|
public static void setProtectedValue(Class c, String field, Object newValue)
|
||||||
|
{
|
||||||
setProtectedValue(c, null, field, newValue);
|
setProtectedValue(c, null, field, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setProtectedValue(Class c, Object o, String field, Object newValue) {
|
public static void setProtectedValue(Class c, Object o, String field, Object newValue)
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
Field f = c.getDeclaredField(field);
|
Field f = c.getDeclaredField(field);
|
||||||
|
|
||||||
@ -28,20 +33,25 @@ public class ReflectionUtil {
|
|||||||
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
|
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
|
||||||
|
|
||||||
f.set(o, newValue);
|
f.set(o, newValue);
|
||||||
} catch (NoSuchFieldException ex) {
|
}
|
||||||
System.out.println("*** " + c.getName() + ":" + ex);
|
catch (NoSuchFieldException | IllegalAccessException ex)
|
||||||
} catch (IllegalAccessException ex) {
|
{
|
||||||
System.out.println("*** " + c.getName() + ":" + ex);
|
System.out.println("*** " + c.getName() + ":" + ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T getProtectedValue(Object obj, String fieldName) {
|
public static <T> T getProtectedValue(Object obj, String fieldName)
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
Class c = obj.getClass();
|
Class c = obj.getClass();
|
||||||
while(c != Object.class) {
|
while (c != Object.class)
|
||||||
|
{
|
||||||
Field[] fields = c.getDeclaredFields();
|
Field[] fields = c.getDeclaredFields();
|
||||||
for(Field f : fields) {
|
for (Field f : fields)
|
||||||
if (f.getName() == fieldName) {
|
{
|
||||||
|
if (f.getName() == fieldName)
|
||||||
|
{
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
return (T) f.get(obj);
|
return (T) f.get(obj);
|
||||||
}
|
}
|
||||||
@ -50,47 +60,58 @@ public class ReflectionUtil {
|
|||||||
}
|
}
|
||||||
System.out.println("*** " + obj.getClass().getName() + ":No such field");
|
System.out.println("*** " + obj.getClass().getName() + ":No such field");
|
||||||
return null;
|
return null;
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
System.out.println("*** " + obj.getClass().getName() + ":" + ex);
|
System.out.println("*** " + obj.getClass().getName() + ":" + ex);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T getProtectedValue(Class c, String field) {
|
public static <T> T getProtectedValue(Class c, String field)
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
Field f = c.getDeclaredField(field);
|
Field f = c.getDeclaredField(field);
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
return (T) f.get(c);
|
return (T) f.get(c);
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
System.out.println("*** " + c.getName() + ":" + ex);
|
System.out.println("*** " + c.getName() + ":" + ex);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object invokeProtectedMethod(Class c, String method, Object... args) {
|
public static Object invokeProtectedMethod(Class c, String method, Object... args)
|
||||||
|
{
|
||||||
return invokeProtectedMethod(c, null, method, args);
|
return invokeProtectedMethod(c, null, method, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object invokeProtectedMethod(Object o, String method, Object... args) {
|
public static Object invokeProtectedMethod(Object o, String method, Object... args)
|
||||||
|
{
|
||||||
return invokeProtectedMethod(o.getClass(), o, method, args);
|
return invokeProtectedMethod(o.getClass(), o, method, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object invokeProtectedMethod(Class c, Object o, String method, Object... args) {
|
public static Object invokeProtectedMethod(Class c, Object o, String method, Object... args)
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
Class[] pTypes = new Class[args.length];
|
Class[] pTypes = new Class[args.length];
|
||||||
for(int i = 0; i < args.length; i++) {
|
for (int i = 0; i < args.length; i++)
|
||||||
if (args[i] instanceof Integer) {
|
{
|
||||||
|
if (args[i] instanceof Integer)
|
||||||
pTypes[i] = int.class;
|
pTypes[i] = int.class;
|
||||||
} else {
|
else
|
||||||
pTypes[i] = args[i].getClass();
|
pTypes[i] = args[i].getClass();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Method m = c.getDeclaredMethod(method, pTypes);
|
Method m = c.getDeclaredMethod(method, pTypes);
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
return m.invoke(o, args);
|
return m.invoke(o, args);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex)
|
||||||
|
{
|
||||||
System.out.println("*** " + c.getName() + "." + method + "(): " + ex);
|
System.out.println("*** " + c.getName() + "." + method + "(): " + ex);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
@ -0,0 +1,210 @@
|
|||||||
|
package com.ryanmichela.sshd;
|
||||||
|
|
||||||
|
import com.ryanmichela.sshd.jline.ConsoleShellFactory;
|
||||||
|
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
|
||||||
|
import org.apache.sshd.common.session.helpers.AbstractSession;
|
||||||
|
import org.apache.sshd.contrib.server.subsystem.sftp.SimpleAccessControlSftpEventListener;
|
||||||
|
import org.apache.sshd.server.SshServer;
|
||||||
|
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||||
|
import org.apache.sshd.server.session.ServerSession;
|
||||||
|
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
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.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2013 Ryan Michela
|
||||||
|
*/
|
||||||
|
public class SshdPlugin extends JavaPlugin
|
||||||
|
{
|
||||||
|
private SshServer sshd;
|
||||||
|
public static SshdPlugin instance;
|
||||||
|
|
||||||
|
public static List<ConfigurationSection> GetSections(ConfigurationSection source)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<ConfigurationSection> nodes = new ArrayList<ConfigurationSection>();
|
||||||
|
for (String key : source.getKeys(false))
|
||||||
|
{
|
||||||
|
if (source.isConfigurationSection(key))
|
||||||
|
nodes.add(source.getConfigurationSection(key));
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onLoad()
|
||||||
|
{
|
||||||
|
saveDefaultConfig();
|
||||||
|
File authorizedKeys = new File(getDataFolder(), "authorized_keys");
|
||||||
|
if (!authorizedKeys.exists())
|
||||||
|
authorizedKeys.mkdirs();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File motd = new File(getDataFolder(), "motd.txt");
|
||||||
|
if (!motd.exists())
|
||||||
|
{
|
||||||
|
InputStream link = (getClass().getResourceAsStream("/motd.txt"));
|
||||||
|
Files.copy(link, motd.getAbsoluteFile().toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't go any lower than INFO or SSHD will cause a stack overflow exception.
|
||||||
|
// SSHD will log that it wrote bites to the output stream, which writes
|
||||||
|
// bytes to the output stream - ad nauseaum.
|
||||||
|
getLogger().setLevel(Level.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onEnable()
|
||||||
|
{
|
||||||
|
instance = this;
|
||||||
|
|
||||||
|
sshd = SshServer.setUpDefaultServer();
|
||||||
|
sshd.setPort(getConfig().getInt("Port", 1025));
|
||||||
|
String host = getConfig().getString("ListenAddress", "all");
|
||||||
|
sshd.setHost(host.equals("all") ? null : host);
|
||||||
|
|
||||||
|
File hostKey = new File(getDataFolder(), "hostkey");
|
||||||
|
File authorizedKeys = new File(getDataFolder(), "authorized_keys");
|
||||||
|
|
||||||
|
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey.toPath()));
|
||||||
|
sshd.setShellFactory(new ConsoleShellFactory());
|
||||||
|
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
|
||||||
|
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
|
||||||
|
|
||||||
|
if (getConfig().getBoolean("EnableSFTP", false))
|
||||||
|
{
|
||||||
|
// Handle access control for SFTP.
|
||||||
|
SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder();
|
||||||
|
builder.addSftpEventListener(new SimpleAccessControlSftpEventListener()
|
||||||
|
{
|
||||||
|
protected boolean isAccessAllowed(ServerSession session, String remote, Path localpath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConfigurationSection UsernameNamespace = getConfig().getConfigurationSection("Credentials." + session.getUsername() + ".sftp");
|
||||||
|
|
||||||
|
// They don't have SFTP enabled so deny them.
|
||||||
|
if (UsernameNamespace == null || !UsernameNamespace.getBoolean("enabled"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
List<ConfigurationSection> rules = GetSections(UsernameNamespace.getConfigurationSection("rules"));
|
||||||
|
if (rules != null)
|
||||||
|
{
|
||||||
|
for (ConfigurationSection path : rules)
|
||||||
|
{
|
||||||
|
// Check if the requesting path matches
|
||||||
|
if (localpath.toString().matches(path.getName()))
|
||||||
|
{
|
||||||
|
// Check if they have read permissions
|
||||||
|
if (path.getBoolean("readable"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
getLogger().info(String.format("Denied %s read access to \"%s\" matching rule \"%s\"", session.getUsername(), localpath.toString(), path.getName()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UsernameNamespace.getString("default").equalsIgnoreCase("allow");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
// Automatically deny.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isModificationAllowed(ServerSession session, String remote, Path localpath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boolean defaultbool = getConfig().getBoolean("Credentials.$default.sftp.enabled", false);
|
||||||
|
ConfigurationSection UsernameNamespace = getConfig().getConfigurationSection("Credentials." + session.getUsername() + ".sftp");
|
||||||
|
|
||||||
|
// They don't have SFTP enabled so deny them.
|
||||||
|
if (UsernameNamespace == null || !UsernameNamespace.getBoolean("enabled", defaultbool))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check a list of files against a path trying to be accessed.
|
||||||
|
List<ConfigurationSection> rules = GetSections(UsernameNamespace.getConfigurationSection("rules"));
|
||||||
|
if (rules != null)
|
||||||
|
{
|
||||||
|
for (ConfigurationSection path : rules)
|
||||||
|
{
|
||||||
|
// Check if the requesting path matches
|
||||||
|
if (localpath.toString().matches(path.getName()))
|
||||||
|
{
|
||||||
|
// Check if they have read permissions
|
||||||
|
if (path.getBoolean("writeable"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
getLogger().info(String.format("Denied %s modifications to \"%s\" matching rule \"%s\"", session.getUsername(), localpath.toString(), path.getName()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UsernameNamespace.getString("default", "deny").equalsIgnoreCase("allow");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
// Automatically deny.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sshd.setSubsystemFactories(Collections.singletonList(builder.build()));
|
||||||
|
sshd.setFileSystemFactory(new VirtualFileSystemFactory(FileSystems.getDefault().getPath(getDataFolder().getAbsolutePath()).getParent().getParent()));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getCommand("mkpasswd").setExecutor(new MkpasswdCommand());
|
||||||
|
|
||||||
|
sshd.setCommandFactory(new ConsoleCommandFactory());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sshd.start();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
getLogger().log(Level.SEVERE, "Failed to start SSH server! ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onDisable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Terminate any active sessions
|
||||||
|
for (AbstractSession as : sshd.getActiveSessions())
|
||||||
|
as.close(true);
|
||||||
|
// Pass "true" to stop immediately!
|
||||||
|
sshd.stop(true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package com.ryanmichela.sshd;
|
||||||
|
|
||||||
|
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 java.io.Serializable;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
import java.util.logging.StreamHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2014 Ryan Michela
|
||||||
|
*/
|
||||||
|
public class StreamHandlerAppender implements Appender
|
||||||
|
{
|
||||||
|
|
||||||
|
private StreamHandler streamHandler;
|
||||||
|
private UUID uuid;
|
||||||
|
|
||||||
|
public StreamHandlerAppender(StreamHandler streamHandler)
|
||||||
|
{
|
||||||
|
this.streamHandler = streamHandler;
|
||||||
|
uuid = UUID.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(LogEvent logEvent)
|
||||||
|
{
|
||||||
|
java.util.logging.Level level;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
String message = logEvent.getMessage().getFormattedMessage();
|
||||||
|
LogRecord logRecord = new LogRecord(level, message);
|
||||||
|
streamHandler.publish(logRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return "StreamHandlerAppender:" + uuid.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Layout<? extends Serializable> getLayout()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean ignoreExceptions()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ErrorHandler getHandler()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHandler(ErrorHandler errorHandler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public State getState() {
|
||||||
|
// Todo: i am not sure what is this
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
// Todo: i am not sure what is this
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStarted()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStopped()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -5,29 +5,42 @@ import java.util.concurrent.ExecutionException;
|
|||||||
/**
|
/**
|
||||||
* Copyright 2013 Ryan Michela
|
* Copyright 2013 Ryan Michela
|
||||||
*/
|
*/
|
||||||
public abstract class Waitable<T> implements Runnable {
|
public abstract class Waitable<T> implements Runnable
|
||||||
private enum Status {
|
{
|
||||||
|
|
||||||
|
private enum Status
|
||||||
|
{
|
||||||
WAITING,
|
WAITING,
|
||||||
RUNNING,
|
RUNNING,
|
||||||
FINISHED,
|
FINISHED,
|
||||||
}
|
}
|
||||||
|
|
||||||
Throwable t = null;
|
Throwable t = null;
|
||||||
T value = null;
|
T value = null;
|
||||||
Status status = Status.WAITING;
|
Status status = Status.WAITING;
|
||||||
|
|
||||||
public final void run() {
|
public final void run()
|
||||||
synchronized (this) {
|
{
|
||||||
if (status != Status.WAITING) {
|
synchronized (this)
|
||||||
|
{
|
||||||
|
if (status != Status.WAITING)
|
||||||
throw new IllegalStateException("Invalid state " + status);
|
throw new IllegalStateException("Invalid state " + status);
|
||||||
}
|
|
||||||
status = Status.RUNNING;
|
status = Status.RUNNING;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
|
try
|
||||||
|
{
|
||||||
value = evaluate();
|
value = evaluate();
|
||||||
} catch (Throwable t) {
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
this.t = t;
|
this.t = t;
|
||||||
} finally {
|
}
|
||||||
synchronized (this) {
|
finally
|
||||||
|
{
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
status = Status.FINISHED;
|
status = Status.FINISHED;
|
||||||
this.notifyAll();
|
this.notifyAll();
|
||||||
}
|
}
|
||||||
@ -36,13 +49,14 @@ public abstract class Waitable<T> implements Runnable {
|
|||||||
|
|
||||||
protected abstract T evaluate();
|
protected abstract T evaluate();
|
||||||
|
|
||||||
public synchronized T get() throws InterruptedException, ExecutionException {
|
public synchronized T get() throws InterruptedException, ExecutionException
|
||||||
while (status != Status.FINISHED) {
|
{
|
||||||
|
while (status != Status.FINISHED)
|
||||||
this.wait();
|
this.wait();
|
||||||
}
|
|
||||||
if (t != null) {
|
if (t != null)
|
||||||
throw new ExecutionException(t);
|
throw new ExecutionException(t);
|
||||||
}
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,193 @@
|
|||||||
|
package com.ryanmichela.sshd.implementations.commandsenders;
|
||||||
|
|
||||||
|
import com.ryanmichela.sshd.SshdPlugin;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
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.jline.ConsoleShellFactory;
|
||||||
|
import com.ryanmichela.sshd.ConsoleLogFormatter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class SSHDCommandSender implements ConsoleCommandSender, CommandSender
|
||||||
|
{
|
||||||
|
private final PermissibleBase perm = new PermissibleBase(this);
|
||||||
|
private final SSHDConversationTracker conversationTracker = new SSHDConversationTracker();
|
||||||
|
// Set by the upstream allocating function
|
||||||
|
public ConsoleShellFactory.ConsoleShell console;
|
||||||
|
|
||||||
|
public void sendMessage(String message)
|
||||||
|
{
|
||||||
|
this.sendRawMessage(message + "\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
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.println(ConsoleLogFormatter.ColorizeString(message).replace("\n", "\n\r"));
|
||||||
|
this.console.ConsoleReader.print(this.console.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.instance.getLogger().log(Level.SEVERE, "Error sending message to SSHDCommandSender", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendRawMessage(UUID uuid, String s) {
|
||||||
|
sendRawMessage(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessage(String[] messages)
|
||||||
|
{
|
||||||
|
Arrays.asList(messages).forEach(this::sendMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(UUID uuid, String s) {
|
||||||
|
sendMessage(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(UUID uuid, String... strings) {
|
||||||
|
sendMessage(strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return "SSHD Console";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOp()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOp(boolean value)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException("Cannot change operator status of server console");
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Server getServer()
|
||||||
|
{
|
||||||
|
return Bukkit.getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandSender.Spigot spigot()
|
||||||
|
{
|
||||||
|
return ((CommandSender)this).spigot();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package com.ryanmichela.sshd.implementations.commandsenders;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -1,43 +1,55 @@
|
|||||||
package com.ryanmichela.sshd;
|
package com.ryanmichela.sshd.jline;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2013 Ryan Michela
|
* Copyright 2013 Ryan Michela
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import com.ryanmichela.sshd.ReflectionUtil;
|
||||||
|
import com.ryanmichela.sshd.SshdPlugin;
|
||||||
|
import com.ryanmichela.sshd.Waitable;
|
||||||
|
import jline.console.completer.Completer;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.CommandMap;
|
import org.bukkit.command.CommandMap;
|
||||||
import org.bukkit.craftbukkit.libs.jline.console.completer.Completer;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class ConsoleCommandCompleter implements Completer {
|
public class ConsoleCommandCompleter implements Completer
|
||||||
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
|
{
|
||||||
Waitable<List<String>> waitable = new Waitable<List<String>>() {
|
public int complete(final String buffer, final int cursor, final List<CharSequence> candidates)
|
||||||
|
{
|
||||||
|
Waitable<List<String>> waitable = new Waitable<List<String>>()
|
||||||
|
{
|
||||||
@Override
|
@Override
|
||||||
protected List<String> evaluate() {
|
protected List<String> evaluate()
|
||||||
|
{
|
||||||
CommandMap commandMap = ReflectionUtil.getProtectedValue(Bukkit.getServer(), "commandMap");
|
CommandMap commandMap = ReflectionUtil.getProtectedValue(Bukkit.getServer(), "commandMap");
|
||||||
return commandMap.tabComplete(Bukkit.getServer().getConsoleSender(), buffer);
|
return commandMap.tabComplete(Bukkit.getServer().getConsoleSender(), buffer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Bukkit.getScheduler().runTask(SshdPlugin.instance, waitable);
|
Bukkit.getScheduler().runTask(SshdPlugin.instance, waitable);
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
List<String> offers = waitable.get();
|
List<String> offers = waitable.get();
|
||||||
if (offers == null) {
|
if (offers == null)
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
|
||||||
candidates.addAll(offers);
|
candidates.addAll(offers);
|
||||||
|
|
||||||
final int lastSpace = buffer.lastIndexOf(' ');
|
final int lastSpace = buffer.lastIndexOf(' ');
|
||||||
if (lastSpace == -1) {
|
if (lastSpace == -1)
|
||||||
return cursor - buffer.length();
|
return cursor - buffer.length();
|
||||||
} else {
|
else
|
||||||
return cursor - (buffer.length() - lastSpace - 1);
|
return cursor - (buffer.length() - lastSpace - 1);
|
||||||
}
|
}
|
||||||
} catch (ExecutionException e) {
|
catch (ExecutionException e)
|
||||||
|
{
|
||||||
SshdPlugin.instance.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e);
|
SshdPlugin.instance.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e);
|
||||||
} catch (InterruptedException e) {
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
return cursor;
|
return cursor;
|
@ -0,0 +1,246 @@
|
|||||||
|
package com.ryanmichela.sshd.jline;
|
||||||
|
|
||||||
|
import com.ryanmichela.sshd.*;
|
||||||
|
import com.ryanmichela.sshd.implementations.commandsenders.SSHDCommandSender;
|
||||||
|
import jline.console.ConsoleReader;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.core.Logger;
|
||||||
|
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 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.net.UnknownHostException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.StreamHandler;
|
||||||
|
|
||||||
|
public class ConsoleShellFactory implements ShellFactory
|
||||||
|
{
|
||||||
|
|
||||||
|
public Command createShell(ChannelSession cs)
|
||||||
|
{
|
||||||
|
return new ConsoleShell();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConsoleShell implements Command, Runnable
|
||||||
|
{
|
||||||
|
|
||||||
|
private InputStream in;
|
||||||
|
private OutputStream out;
|
||||||
|
private OutputStream err;
|
||||||
|
private ExitCallback callback;
|
||||||
|
private Environment environment;
|
||||||
|
private Thread thread;
|
||||||
|
private String Username;
|
||||||
|
|
||||||
|
StreamHandlerAppender streamHandlerAppender;
|
||||||
|
public ConsoleReader ConsoleReader;
|
||||||
|
public SSHDCommandSender SshdCommandSender;
|
||||||
|
|
||||||
|
public InputStream getIn()
|
||||||
|
{
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream getOut()
|
||||||
|
{
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream getErr()
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Environment getEnvironment()
|
||||||
|
{
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputStream(InputStream in)
|
||||||
|
{
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputStream(OutputStream out)
|
||||||
|
{
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorStream(OutputStream err)
|
||||||
|
{
|
||||||
|
this.err = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExitCallback(ExitCallback callback)
|
||||||
|
{
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(ChannelSession cs, Environment env) throws IOException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String username = env.getEnv().get(Environment.ENV_USER);
|
||||||
|
Optional<String> optcred = PermissionUtil.GetCredential(username, "console");
|
||||||
|
// They don't have access.
|
||||||
|
if (optcred.isPresent() && !optcred.get().contains("R"))
|
||||||
|
{
|
||||||
|
cs.close(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SshdPlugin.instance.getLogger().warning("There is no $default pseudo-user under credential, allowing unrestricted access...");
|
||||||
|
|
||||||
|
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(), this.ConsoleReader);
|
||||||
|
this.streamHandlerAppender = new StreamHandlerAppender(streamHandler);
|
||||||
|
|
||||||
|
((Logger)LogManager.getRootLogger()).addAppender(this.streamHandlerAppender);
|
||||||
|
|
||||||
|
this.environment = env;
|
||||||
|
this.Username = username;
|
||||||
|
this.SshdCommandSender = new SSHDCommandSender();
|
||||||
|
this.SshdCommandSender.console = this;
|
||||||
|
thread = new Thread(this, "SSHD ConsoleShell " + username);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new IOException("Error starting shell", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy(ChannelSession cs) { ((Logger)LogManager.getRootLogger()).removeAppender(this.streamHandlerAppender); }
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!SshdPlugin.instance.getConfig().getString("Mode", "DEFAULT").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");
|
||||||
|
Optional<String> optcred = PermissionUtil.GetCredential(this.Username, "console");
|
||||||
|
|
||||||
|
if (optcred.isPresent() && !optcred.get().contains("W"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTask(
|
||||||
|
SshdPlugin.instance, () ->
|
||||||
|
{
|
||||||
|
if (SshdPlugin.instance.getConfig().getString("Mode", "DEFAULT").equals("RPC") && command.startsWith("rpc"))
|
||||||
|
{
|
||||||
|
// NO ECHO NO PREAMBLE AND SHIT
|
||||||
|
String cmd = command.substring("rpc".length() + 1, command.length());
|
||||||
|
Bukkit.dispatchCommand(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.instance.getLogger().info("<" + this.Username + "> " + command);
|
||||||
|
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Bukkit.dispatchCommand(this.SshdCommandSender, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SshdPlugin.instance.getLogger().log(Level.SEVERE, "Error processing command from SSH", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SshdPlugin.instance.getLogger().log(Level.INFO, this.Username + " disconnected from SSH.");
|
||||||
|
callback.onExit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String GetHostname()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return InetAddress.getLocalHost().getHostName();
|
||||||
|
}
|
||||||
|
catch (UnknownHostException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SshdPlugin.instance.getLogger().log(Level.INFO, "The above stacktrace can be ignored, you likely have a misconfigured system hosts file.");
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printPreamble(ConsoleReader cr) throws IOException
|
||||||
|
{
|
||||||
|
File f = new File(SshdPlugin.instance.getDataFolder(), "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)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SshdPlugin.instance.getLogger().log(Level.WARNING, "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.
|
||||||
|
cr.println("Connected to: " + this.GetHostname() + " (" + Bukkit.getServer().getName() + ")\r");
|
||||||
|
cr.println(ConsoleLogFormatter.ColorizeString(Bukkit.getServer().getMotd()).replaceAll("\n", "\r\n"));
|
||||||
|
cr.println("\r");
|
||||||
|
cr.println("Type 'exit' to exit the shell." + "\r");
|
||||||
|
cr.println("===============================================" + "\r");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.ryanmichela.sshd.jline;
|
||||||
|
|
||||||
|
import jline.TerminalSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2013 Ryan Michela
|
||||||
|
*/
|
||||||
|
public class SshTerminal extends TerminalSupport
|
||||||
|
{
|
||||||
|
public SshTerminal()
|
||||||
|
{
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() throws Exception
|
||||||
|
{
|
||||||
|
setAnsiSupported(true);
|
||||||
|
setEchoEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
@ -22,10 +22,7 @@ public class PluginSlf4jFactory implements ILoggerFactory {
|
|||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
private boolean isEnabled(Level level) {
|
private boolean isEnabled(Level level) {
|
||||||
if (SshdPlugin.instance != null) {
|
return SshdPlugin.instance != null && SshdPlugin.instance.getLogger().isLoggable(level);
|
||||||
return SshdPlugin.instance.getLogger().isLoggable(level);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void log(Level level, String s, Object[] objects) {
|
private void log(Level level, String s, Object[] objects) {
|
87
Minecraft-SSHD-Bukkit/src/main/resources/config.yml
Normal file
87
Minecraft-SSHD-Bukkit/src/main/resources/config.yml
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# 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. Note that anything *below* 1024 will require you to run
|
||||||
|
# the whole minecraft server with elevated privileges (NOT RECOMMENDED).
|
||||||
|
Port: 1025
|
||||||
|
|
||||||
|
# Operational mode. Don't touch if you don't know what you're doing. Can be either DEFAULT or RPC
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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 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.
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
# The defaults for any user who does not have a specific section.
|
||||||
|
# Specific user permissions override the $default pseudo-user.
|
||||||
|
$default:
|
||||||
|
# Whether they can read or write to the console
|
||||||
|
console: RW
|
||||||
|
# SFTP access for anyone.
|
||||||
|
sftp:
|
||||||
|
# Whether sftp is allowed at all.
|
||||||
|
enabled: true
|
||||||
|
# Whether to allow or deny by default
|
||||||
|
default: allow
|
||||||
|
# specific rules for directories
|
||||||
|
rules:
|
||||||
|
# Deny the SSHD config folder by default as an example.
|
||||||
|
"*SSHD/*":
|
||||||
|
readable: false
|
||||||
|
writeable: false
|
||||||
|
|
||||||
|
# Username (should match SSH key if using key-based authentication)
|
||||||
|
justasic:
|
||||||
|
# Password hash from /mkpasswd command
|
||||||
|
password: $2a$10$Oqk83FrypRrMF35EDeoQDuidJOQEWBE0joEQ7MJFi/Oeg26wQ3fm2
|
||||||
|
# Whether they can read, write, or have read/write permissions to console.
|
||||||
|
console: RW
|
||||||
|
# SFTP access for this user.
|
||||||
|
sftp:
|
||||||
|
# Whether SFTP is enabled for this user.
|
||||||
|
enabled: true
|
||||||
|
# Whether to deny access by default or allow access by default
|
||||||
|
default: allow
|
||||||
|
# Rules regarding their SFTP access.
|
||||||
|
# These rules are relative to the server root.
|
||||||
|
# This acts as a chroot for the server root.
|
||||||
|
# Each path can be an absolute path or a regular expression.
|
||||||
|
rules:
|
||||||
|
"/path/to/file":
|
||||||
|
# Whether the user can read the file over SFTP
|
||||||
|
readable: true
|
||||||
|
# Whether the user can write/modify the file over SFTP
|
||||||
|
writeable: true
|
||||||
|
"/path/to/regex/*":
|
||||||
|
readable: true
|
||||||
|
writeable: false
|
||||||
|
"/path/to/directory/":
|
||||||
|
readable: false
|
||||||
|
writeable: true
|
||||||
|
"/another/example/path":
|
||||||
|
readable: false
|
||||||
|
writeable: false
|
8
Minecraft-SSHD-Bukkit/src/main/resources/motd.txt
Normal file
8
Minecraft-SSHD-Bukkit/src/main/resources/motd.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
§l§4 _____ _____ _ _ _____§r
|
||||||
|
§l§6 / ____/ ____| | | | __ \§r
|
||||||
|
§l§2| (___| (___ | |__| | | | |§r
|
||||||
|
§l§3 \___ \\___ \| __ | | | |§r
|
||||||
|
§l§9 ____) |___) | | | | |__| |§r
|
||||||
|
§l§5|_____/_____/|_| |_|_____/§r
|
||||||
|
|
||||||
|
===============================================
|
9
Minecraft-SSHD-Bukkit/src/main/resources/plugin.yml
Normal file
9
Minecraft-SSHD-Bukkit/src/main/resources/plugin.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
name: SSHD
|
||||||
|
version: ${project.version}
|
||||||
|
author: Ryan Michela, Haarolean, toxuin, Justin Crawford, Zachery Coleman
|
||||||
|
main: com.ryanmichela.sshd.SshdPlugin
|
||||||
|
load: STARTUP
|
||||||
|
commands:
|
||||||
|
mkpasswd:
|
||||||
|
description: Make a SSHD password hash
|
||||||
|
usage: /mkpasswd <hash|help> <password>
|
101
README.md
101
README.md
@ -1,4 +1,101 @@
|
|||||||
Bukkit-SSHD
|
Minecraft-SSHD
|
||||||
===========
|
===========
|
||||||
|
|
||||||
An SSHD daemon embedded in a Bukkit plugin.
|
[![GitHub license](https://img.shields.io/github/license/Justasic/Minecraft-SSHD)](https://github.com/Justasic/Minecraft-SSHD/blob/master/LICENSE)
|
||||||
|
|
||||||
|
<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 Minecraft 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")
|
||||||
|
|
||||||
|
## Our Discord
|
||||||
|
https://discord.gg/DtrJFn
|
||||||
|
BIN
docs/console.png
Normal file
BIN
docs/console.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 355 KiB |
BIN
docs/session.png
Normal file
BIN
docs/session.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 266 KiB |
BIN
docs/ssh_logo.png
Normal file
BIN
docs/ssh_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
133
docs/ssh_logo.svg
Normal file
133
docs/ssh_logo.svg
Normal 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">></tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
152
pom.xml
152
pom.xml
@ -5,87 +5,121 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>com.ryanmichela</groupId>
|
<groupId>com.ryanmichela</groupId>
|
||||||
<artifactId>SSHD</artifactId>
|
<artifactId>sshd</artifactId>
|
||||||
<version>1.1</version>
|
<description>Minecraft-SSHD: The SSH daemon for Minecraft servers.</description>
|
||||||
<url>http://dev.bukkit.org/server-mods/sshd/</url>
|
<modules>
|
||||||
|
<module>Minecraft-SSHD-Bukkit</module>
|
||||||
|
</modules>
|
||||||
|
<version>2.1.0</version>
|
||||||
|
<url>https://git.limework.net/Limework/Minecraft-SSHD/</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<!-- Repositories -->
|
<!-- Repositories -->
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>bukkit-repo</id>
|
<id>spigot-repo</id>
|
||||||
<url>http://repo.bukkit.org/content/groups/public/</url>
|
<url>https://hub.spigotmc.org/nexus/content/groups/public</url>
|
||||||
</repository>
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<!-- License -->
|
<!-- License -->
|
||||||
<licenses>
|
<licenses>
|
||||||
<license>
|
<license>
|
||||||
<name>GPL2</name>
|
<name>Apache-2.0</name>
|
||||||
<url>http://www.gnu.org/licenses/gpl-2.0.html</url>
|
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||||
</license>
|
</license>
|
||||||
</licenses>
|
</licenses>
|
||||||
|
|
||||||
<!-- Dependencies -->
|
<!-- Dependencies -->
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bukkit</groupId>
|
<groupId>org.apache.sshd</groupId>
|
||||||
<artifactId>craftbukkit</artifactId>
|
<artifactId>sshd-core</artifactId>
|
||||||
<version>1.7.9-R0.2</version>
|
<version>2.9.0</version>
|
||||||
<scope>provided</scope>
|
<scope>compile</scope>
|
||||||
<type>jar</type>
|
<type>jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.sshd</groupId>
|
<groupId>org.apache.sshd</groupId>
|
||||||
<artifactId>sshd-core</artifactId>
|
<artifactId>sshd-mina</artifactId>
|
||||||
<version>0.9.0</version>
|
<version>2.9.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.sshd</groupId>
|
||||||
|
<artifactId>sshd-contrib</artifactId>
|
||||||
|
<version>2.9.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.sshd</groupId>
|
||||||
|
<artifactId>sshd-common</artifactId>
|
||||||
|
<version>2.9.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
<type>jar</type>
|
<type>jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.sshd</groupId>
|
||||||
|
<artifactId>sshd-sftp</artifactId>
|
||||||
|
<version>2.9.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.i2p.crypto</groupId>
|
||||||
|
<artifactId>eddsa</artifactId>
|
||||||
|
<version>0.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.mina</groupId>
|
||||||
|
<artifactId>mina-core</artifactId>
|
||||||
|
<version>2.2.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>2.0.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-jdk14</artifactId>
|
||||||
|
<version>2.0.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>jline</groupId>
|
||||||
|
<artifactId>jline</artifactId>
|
||||||
|
<version>2.14.6</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-core</artifactId>
|
||||||
|
<version>2.20.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-api</artifactId>
|
||||||
|
<version>2.20.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-codec</groupId>
|
||||||
|
<artifactId>commons-codec</artifactId>
|
||||||
|
<version>1.15</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<!-- Build -->
|
<packaging>pom</packaging>
|
||||||
<build>
|
</project>
|
||||||
<plugins>
|
|
||||||
<!-- Shade plugin -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
|
||||||
<version>1.5</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>shade</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
<configuration>
|
|
||||||
<filters>
|
|
||||||
<filter>
|
|
||||||
<artifact>*:*</artifact>
|
|
||||||
<excludes>
|
|
||||||
<exclude>META-INF/*.SF</exclude>
|
|
||||||
<exclude>META-INF/*.DSA</exclude>
|
|
||||||
<exclude>META-INF/*.RSA</exclude>
|
|
||||||
</excludes>
|
|
||||||
</filter>
|
|
||||||
</filters>
|
|
||||||
<minimizeJar>true</minimizeJar>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<!-- Compile plugin -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<version>3.0</version>
|
|
||||||
<configuration>
|
|
||||||
<source>1.6</source>
|
|
||||||
<target>1.6</target>
|
|
||||||
<showDeprecation>true</showDeprecation>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
</project>
|
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
package com.ryanmichela.sshd;
|
|
||||||
|
|
||||||
import org.apache.sshd.server.PasswordAuthenticator;
|
|
||||||
import org.apache.sshd.server.session.ServerSession;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright 2013 Ryan Michela
|
|
||||||
*/
|
|
||||||
public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
|
|
||||||
private Map<String, Integer> failCounts = new HashMap<String, Integer>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean authenticate(String username, String password, ServerSession serverSession) {
|
|
||||||
if (SshdPlugin.instance.getConfig().getString("credentials." + username).equals(password)) {
|
|
||||||
failCounts.put(username, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
SshdPlugin.instance.getLogger().info("Failed login for " + username + " using password authentication.");
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(3000);
|
|
||||||
if (failCounts.containsKey(username)) {
|
|
||||||
failCounts.put(username, failCounts.get(username) + 1);
|
|
||||||
} else {
|
|
||||||
failCounts.put(username, 1);
|
|
||||||
}
|
|
||||||
if (failCounts.get(username) >= 3) {
|
|
||||||
failCounts.put(username, 0);
|
|
||||||
serverSession.close(true);
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
package com.ryanmichela.sshd;
|
|
||||||
|
|
||||||
import org.apache.sshd.server.Command;
|
|
||||||
import org.apache.sshd.server.CommandFactory;
|
|
||||||
import org.apache.sshd.server.Environment;
|
|
||||||
import org.apache.sshd.server.ExitCallback;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright 2013 Ryan Michela
|
|
||||||
*/
|
|
||||||
public class ConsoleCommandFactory implements CommandFactory {
|
|
||||||
@Override
|
|
||||||
public Command createCommand(String command) {
|
|
||||||
return new ConsoleCommand(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ConsoleCommand implements Command {
|
|
||||||
private String command;
|
|
||||||
|
|
||||||
private InputStream in;
|
|
||||||
private OutputStream out;
|
|
||||||
private OutputStream err;
|
|
||||||
private ExitCallback callback;
|
|
||||||
|
|
||||||
public ConsoleCommand(String command) {
|
|
||||||
this.command = command;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInputStream(InputStream in) {
|
|
||||||
this.in = in;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOutputStream(OutputStream out) {
|
|
||||||
this.out = out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setErrorStream(OutputStream err) {
|
|
||||||
this.err = err;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExitCallback(ExitCallback callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(Environment environment) throws IOException {
|
|
||||||
try {
|
|
||||||
SshdPlugin.instance.getLogger().info("[U: " + environment.getEnv().get(Environment.ENV_USER) + "] " + command);
|
|
||||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
|
||||||
} catch (Exception e) {
|
|
||||||
SshdPlugin.instance.getLogger().severe("Error processing command from SSH -" + e.getMessage());
|
|
||||||
} finally {
|
|
||||||
callback.onExit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package com.ryanmichela.sshd;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright 2013 Ryan Michela
|
|
||||||
*/
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.logging.Formatter;
|
|
||||||
import java.util.logging.LogRecord;
|
|
||||||
|
|
||||||
public class ConsoleLogFormatter extends Formatter {
|
|
||||||
|
|
||||||
private SimpleDateFormat dateFormat;
|
|
||||||
|
|
||||||
public ConsoleLogFormatter() {
|
|
||||||
this.dateFormat = new SimpleDateFormat("HH:mm:ss");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String format(LogRecord logrecord) {
|
|
||||||
StringBuilder stringbuilder = new StringBuilder();
|
|
||||||
|
|
||||||
stringbuilder.append(" [");
|
|
||||||
stringbuilder.append(this.dateFormat.format(Long.valueOf(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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
|||||||
package com.ryanmichela.sshd;
|
|
||||||
|
|
||||||
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.Environment;
|
|
||||||
import org.apache.sshd.server.ExitCallback;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.craftbukkit.libs.jline.console.ConsoleReader;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.logging.StreamHandler;
|
|
||||||
|
|
||||||
public class ConsoleShellFactory implements Factory<Command> {
|
|
||||||
|
|
||||||
public Command create() {
|
|
||||||
return new ConsoleShell();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ConsoleShell implements Command, Runnable {
|
|
||||||
|
|
||||||
private InputStream in;
|
|
||||||
private OutputStream out;
|
|
||||||
private OutputStream err;
|
|
||||||
private ExitCallback callback;
|
|
||||||
private Environment environment;
|
|
||||||
private Thread thread;
|
|
||||||
|
|
||||||
StreamHandlerAppender streamHandlerAppender;
|
|
||||||
ConsoleReader consoleReader;
|
|
||||||
|
|
||||||
public InputStream getIn() {
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutputStream getOut() {
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutputStream getErr() {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Environment getEnvironment() {
|
|
||||||
return environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInputStream(InputStream in) {
|
|
||||||
this.in = in;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOutputStream(OutputStream out) {
|
|
||||||
this.out = out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setErrorStream(OutputStream err) {
|
|
||||||
this.err = err;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExitCallback(ExitCallback callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start(Environment env) throws IOException {
|
|
||||||
|
|
||||||
try {
|
|
||||||
consoleReader = new ConsoleReader(in, new FlushyOutputStream(out), new SshTerminal());
|
|
||||||
consoleReader.setExpandEvents(true);
|
|
||||||
consoleReader.addCompleter(new ConsoleCommandCompleter());
|
|
||||||
|
|
||||||
StreamHandler streamHandler = new FlushyStreamHandler(out, new ConsoleLogFormatter(), consoleReader);
|
|
||||||
streamHandlerAppender = new StreamHandlerAppender(streamHandler);
|
|
||||||
|
|
||||||
((Logger) LogManager.getRootLogger()).addAppender(streamHandlerAppender);
|
|
||||||
|
|
||||||
environment = env;
|
|
||||||
thread = new Thread(this, "SSHD ConsoleShell " + env.getEnv().get(Environment.ENV_USER));
|
|
||||||
thread.start();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IOException("Error starting shell", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroy() {
|
|
||||||
((Logger) LogManager.getRootLogger()).removeAppender(streamHandlerAppender);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
String command;
|
|
||||||
try {
|
|
||||||
printPreamble(consoleReader);
|
|
||||||
while(true) {
|
|
||||||
command = consoleReader.readLine("\r>", null);
|
|
||||||
if (command != null) {
|
|
||||||
if (command.equals("exit")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
SshdPlugin.instance.getLogger().info("<" + environment.getEnv().get(Environment.ENV_USER) + "> " + command);
|
|
||||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
SshdPlugin.instance.getLogger().severe("Error processing command from SSH");
|
|
||||||
} finally {
|
|
||||||
callback.onExit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printPreamble(ConsoleReader consoleReader) throws IOException{
|
|
||||||
consoleReader.println(" _____ _____ _ _ _____");
|
|
||||||
consoleReader.println(" / ____/ ____| | | | __ \\");
|
|
||||||
consoleReader.println("| (___| (___ | |__| | | | |");
|
|
||||||
consoleReader.println(" \\___ \\\\___ \\| __ | | | |");
|
|
||||||
consoleReader.println(" ____) |___) | | | | |__| |");
|
|
||||||
consoleReader.println("|_____/_____/|_| |_|_____/");
|
|
||||||
consoleReader.println("Connected to: " + Bukkit.getServer().getName());
|
|
||||||
consoleReader.println("- " + Bukkit.getServer().getMotd());
|
|
||||||
consoleReader.println();
|
|
||||||
consoleReader.println("Type 'exit' to exit the shell.");
|
|
||||||
consoleReader.println("===============================================");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package com.ryanmichela.sshd;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright 2013 Ryan Michela
|
|
||||||
*/
|
|
||||||
public class FlushyOutputStream extends OutputStream {
|
|
||||||
private OutputStream base;
|
|
||||||
|
|
||||||
public FlushyOutputStream(OutputStream base) {
|
|
||||||
this.base = base;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(int b) throws IOException {
|
|
||||||
base.write(b);
|
|
||||||
base.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b) throws IOException {
|
|
||||||
base.write(b);
|
|
||||||
base.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b, int off, int len) throws IOException {
|
|
||||||
base.write(b, off, len);
|
|
||||||
base.flush();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
package com.ryanmichela.sshd;
|
|
||||||
|
|
||||||
import org.apache.mina.util.Base64;
|
|
||||||
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.spec.DSAPublicKeySpec;
|
|
||||||
import java.security.spec.RSAPublicKeySpec;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright 2013 Ryan Michela
|
|
||||||
*/
|
|
||||||
public class PemDecoder extends java.io.BufferedReader {
|
|
||||||
private static final String BEGIN = "^-+\\s*BEGIN.+";
|
|
||||||
private static final String END = "^-+\\s*END.+";
|
|
||||||
private static final String COMMENT = "Comment:";
|
|
||||||
|
|
||||||
public PemDecoder(Reader in) {
|
|
||||||
super(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PublicKey getPemBytes() throws Exception {
|
|
||||||
StringBuffer b64 = new StringBuffer();
|
|
||||||
|
|
||||||
String line = readLine();
|
|
||||||
if (!line.matches(BEGIN)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(line = readLine(); line != null; line = readLine()) {
|
|
||||||
if (!line.matches(END) && !line.startsWith(COMMENT)) {
|
|
||||||
b64.append(line.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return decodePublicKey(b64.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] bytes;
|
|
||||||
private int pos;
|
|
||||||
|
|
||||||
private PublicKey decodePublicKey(String keyLine) throws Exception {
|
|
||||||
bytes = null;
|
|
||||||
pos = 0;
|
|
||||||
|
|
||||||
// look for the Base64 encoded part of the line to decode
|
|
||||||
// both ssh-rsa and ssh-dss begin with "AAAA" due to the length bytes
|
|
||||||
for (String part : keyLine.split(" ")) {
|
|
||||||
if (part.startsWith("AAAA")) {
|
|
||||||
bytes = Base64.decodeBase64(part.getBytes());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bytes == null) {
|
|
||||||
throw new IllegalArgumentException("no Base64 part to decode");
|
|
||||||
}
|
|
||||||
|
|
||||||
String type = decodeType();
|
|
||||||
if (type.equals("ssh-rsa")) {
|
|
||||||
BigInteger e = decodeBigInt();
|
|
||||||
BigInteger m = decodeBigInt();
|
|
||||||
RSAPublicKeySpec spec = new RSAPublicKeySpec(m, e);
|
|
||||||
return KeyFactory.getInstance("RSA").generatePublic(spec);
|
|
||||||
} else if (type.equals("ssh-dss")) {
|
|
||||||
BigInteger p = decodeBigInt();
|
|
||||||
BigInteger q = decodeBigInt();
|
|
||||||
BigInteger g = decodeBigInt();
|
|
||||||
BigInteger y = decodeBigInt();
|
|
||||||
DSAPublicKeySpec spec = new DSAPublicKeySpec(y, p, q, g);
|
|
||||||
return KeyFactory.getInstance("DSA").generatePublic(spec);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("unknown type " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String decodeType() {
|
|
||||||
int len = decodeInt();
|
|
||||||
String type = new String(bytes, pos, len);
|
|
||||||
pos += len;
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int decodeInt() {
|
|
||||||
return ((bytes[pos++] & 0xFF) << 24) | ((bytes[pos++] & 0xFF) << 16)
|
|
||||||
| ((bytes[pos++] & 0xFF) << 8) | (bytes[pos++] & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigInteger decodeBigInt() {
|
|
||||||
int len = decodeInt();
|
|
||||||
byte[] bigIntBytes = new byte[len];
|
|
||||||
System.arraycopy(bytes, pos, bigIntBytes, 0, len);
|
|
||||||
pos += len;
|
|
||||||
return new BigInteger(bigIntBytes);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package com.ryanmichela.sshd;
|
|
||||||
|
|
||||||
import org.apache.commons.lang.ArrayUtils;
|
|
||||||
import org.apache.sshd.server.PublickeyAuthenticator;
|
|
||||||
import org.apache.sshd.server.session.ServerSession;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright 2013 Ryan Michela
|
|
||||||
*/
|
|
||||||
public class PublicKeyAuthenticator implements PublickeyAuthenticator {
|
|
||||||
private File authorizedKeysDir;
|
|
||||||
|
|
||||||
public PublicKeyAuthenticator(File authorizedKeysDir) {
|
|
||||||
this.authorizedKeysDir = authorizedKeysDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean authenticate(String username, PublicKey key, ServerSession session) {
|
|
||||||
byte[] keyBytes = key.getEncoded();
|
|
||||||
File keyFile = new File(authorizedKeysDir, username);
|
|
||||||
|
|
||||||
if (keyFile.exists()) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
FileReader fr = new FileReader(keyFile);
|
|
||||||
PemDecoder pd = new PemDecoder(fr);
|
|
||||||
PublicKey k = pd.getPemBytes();
|
|
||||||
pd.close();
|
|
||||||
|
|
||||||
if (k != null) {
|
|
||||||
if (ArrayUtils.isEquals(key.getEncoded(), k.getEncoded())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SshdPlugin.instance.getLogger().severe("Failed to parse PEM file. " + keyFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
SshdPlugin.instance.getLogger().severe("Failed to process public key " + keyFile.getAbsolutePath() + ". " + e.getMessage());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SshdPlugin.instance.getLogger().warning("Could not locate public key for " + username + ". Make sure the user's key is named the same as their user name without a file extension.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package com.ryanmichela.sshd;
|
|
||||||
|
|
||||||
import org.bukkit.craftbukkit.libs.jline.TerminalSupport;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright 2013 Ryan Michela
|
|
||||||
*/
|
|
||||||
public class SshTerminal extends TerminalSupport {
|
|
||||||
|
|
||||||
protected SshTerminal() {
|
|
||||||
super(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init() throws Exception {
|
|
||||||
setAnsiSupported(true);
|
|
||||||
setEchoEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
package com.ryanmichela.sshd;
|
|
||||||
|
|
||||||
import org.apache.sshd.SshServer;
|
|
||||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright 2013 Ryan Michela
|
|
||||||
*/
|
|
||||||
public class SshdPlugin extends JavaPlugin {
|
|
||||||
private SshServer sshd;
|
|
||||||
public static SshdPlugin instance;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoad() {
|
|
||||||
saveDefaultConfig();
|
|
||||||
File authorizedKeys = new File(getDataFolder(), "authorized_keys");
|
|
||||||
if (!authorizedKeys.exists()) {
|
|
||||||
authorizedKeys.mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't go any lower than INFO or SSHD will cause a stack overflow exception.
|
|
||||||
// SSHD will log that it wrote bites to the output stream, which writes
|
|
||||||
// bytes to the output stream - ad nauseaum.
|
|
||||||
getLogger().setLevel(Level.INFO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable() {
|
|
||||||
instance = this;
|
|
||||||
|
|
||||||
sshd = SshServer.setUpDefaultServer();
|
|
||||||
sshd.setPort(getConfig().getInt("port", 22));
|
|
||||||
|
|
||||||
File hostKey = new File(getDataFolder(), "hostkey");
|
|
||||||
File authorizedKeys = new File(getDataFolder(), "authorized_keys");
|
|
||||||
|
|
||||||
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey.getPath()));
|
|
||||||
sshd.setShellFactory(new ConsoleShellFactory());
|
|
||||||
sshd.setPasswordAuthenticator(new ConfigPasswordAuthenticator());
|
|
||||||
sshd.setPublickeyAuthenticator(new PublicKeyAuthenticator(authorizedKeys));
|
|
||||||
sshd.setCommandFactory(new ConsoleCommandFactory());
|
|
||||||
try {
|
|
||||||
sshd.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
getLogger().severe("Failed to start SSH server! " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisable() {
|
|
||||||
try {
|
|
||||||
sshd.stop();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package com.ryanmichela.sshd;
|
|
||||||
|
|
||||||
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 java.io.Serializable;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.logging.LogRecord;
|
|
||||||
import java.util.logging.StreamHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright 2014 Ryan Michela
|
|
||||||
*/
|
|
||||||
public class StreamHandlerAppender implements Appender {
|
|
||||||
private StreamHandler streamHandler;
|
|
||||||
private UUID uuid;
|
|
||||||
|
|
||||||
public StreamHandlerAppender(StreamHandler streamHandler) {
|
|
||||||
this.streamHandler = streamHandler;
|
|
||||||
uuid = UUID.randomUUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void append(LogEvent logEvent) {
|
|
||||||
java.util.logging.Level level;
|
|
||||||
switch (logEvent.getLevel())
|
|
||||||
{
|
|
||||||
case DEBUG: level = java.util.logging.Level.FINE; break;
|
|
||||||
case INFO: level = java.util.logging.Level.INFO; break;
|
|
||||||
case WARN: level = java.util.logging.Level.WARNING; break;
|
|
||||||
case ERROR: level = java.util.logging.Level.SEVERE; break;
|
|
||||||
default: level = java.util.logging.Level.INFO; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
String message = logEvent.getMessage().getFormattedMessage();
|
|
||||||
|
|
||||||
|
|
||||||
LogRecord logRecord = new LogRecord(level, message);
|
|
||||||
streamHandler.publish(logRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "StreamHandlerAppender:" + uuid.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Layout<? extends Serializable> getLayout() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean ignoreExceptions() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ErrorHandler getHandler() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setHandler(ErrorHandler errorHandler) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isStarted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
# This is the port the SSH server will listen on.
|
|
||||||
port: 22
|
|
||||||
|
|
||||||
# By default, only public key authentication is enabled. This is the most secure mode.
|
|
||||||
# To authorize a user to log in with public key authentication, install their public
|
|
||||||
# PEM certificate in the authorized_users directory. Name the key file with user's user
|
|
||||||
# name (no file extension).
|
|
||||||
|
|
||||||
# For less secure username and password based authentication, complete the sections below.
|
|
||||||
credentials:
|
|
||||||
# user1: password1
|
|
||||||
# user2: password2
|
|
@ -1,4 +0,0 @@
|
|||||||
name: SSHD
|
|
||||||
version: "1.1"
|
|
||||||
author: Ryan Michela
|
|
||||||
main: com.ryanmichela.sshd.SshdPlugin
|
|
Loading…
Reference in New Issue
Block a user