diff --git a/pom.xml b/pom.xml
index 54f10fc..94fcae1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.ryanmichela
SSHD
- 1.1
+ 1.2
http://dev.bukkit.org/server-mods/sshd/
diff --git a/src/main/java/com/ryanmichela/sshd/CidrUtils.java b/src/main/java/com/ryanmichela/sshd/CidrUtils.java
new file mode 100644
index 0000000..e0f4b87
--- /dev/null
+++ b/src/main/java/com/ryanmichela/sshd/CidrUtils.java
@@ -0,0 +1,142 @@
+/*
+* The MIT License
+*
+* Copyright (c) 2013 Edin Dazdarevic (edin.dazdarevic@gmail.com)
+
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*
+* */
+
+package com.ryanmichela.sshd;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class that enables to get an IP range from CIDR specification. It supports
+ * both IPv4 and IPv6.
+ */
+public class CIDRUtils {
+ private final String cidr;
+
+ private InetAddress inetAddress;
+ private InetAddress startAddress;
+ private InetAddress endAddress;
+ private final int prefixLength;
+
+
+ public CIDRUtils(String cidr) throws UnknownHostException {
+
+ this.cidr = cidr;
+
+ /* split CIDR to address and prefix part */
+ if (this.cidr.contains("/")) {
+ int index = this.cidr.indexOf("/");
+ String addressPart = this.cidr.substring(0, index);
+ String networkPart = this.cidr.substring(index + 1);
+
+ inetAddress = InetAddress.getByName(addressPart);
+ prefixLength = Integer.parseInt(networkPart);
+
+ calculate();
+ } else {
+ throw new IllegalArgumentException("not an valid CIDR format!");
+ }
+ }
+
+
+ private void calculate() throws UnknownHostException {
+
+ ByteBuffer maskBuffer;
+ int targetSize;
+ if (inetAddress.getAddress().length == 4) {
+ maskBuffer =
+ ByteBuffer
+ .allocate(4)
+ .putInt(-1);
+ targetSize = 4;
+ } else {
+ maskBuffer = ByteBuffer.allocate(16)
+ .putLong(-1L)
+ .putLong(-1L);
+ targetSize = 16;
+ }
+
+ BigInteger mask = (new BigInteger(1, maskBuffer.array())).not().shiftRight(prefixLength);
+
+ ByteBuffer buffer = ByteBuffer.wrap(inetAddress.getAddress());
+ BigInteger ipVal = new BigInteger(1, buffer.array());
+
+ BigInteger startIp = ipVal.and(mask);
+ BigInteger endIp = startIp.add(mask.not());
+
+ byte[] startIpArr = toBytes(startIp.toByteArray(), targetSize);
+ byte[] endIpArr = toBytes(endIp.toByteArray(), targetSize);
+
+ this.startAddress = InetAddress.getByAddress(startIpArr);
+ this.endAddress = InetAddress.getByAddress(endIpArr);
+
+ }
+
+ private byte[] toBytes(byte[] array, int targetSize) {
+ int counter = 0;
+ List newArr = new ArrayList();
+ while (counter < targetSize && (array.length - 1 - counter >= 0)) {
+ newArr.add(0, array[array.length - 1 - counter]);
+ counter++;
+ }
+
+ int size = newArr.size();
+ for (int i = 0; i < (targetSize - size); i++) {
+
+ newArr.add(0, (byte) 0);
+ }
+
+ byte[] ret = new byte[newArr.size()];
+ for (int i = 0; i < newArr.size(); i++) {
+ ret[i] = newArr.get(i);
+ }
+ return ret;
+ }
+
+ public String getNetworkAddress() {
+
+ return this.startAddress.getHostAddress();
+ }
+
+ public String getBroadcastAddress() {
+ return this.endAddress.getHostAddress();
+ }
+
+ public boolean isInRange(String ipAddress) throws UnknownHostException {
+ InetAddress address = InetAddress.getByName(ipAddress);
+ BigInteger start = new BigInteger(1, this.startAddress.getAddress());
+ BigInteger end = new BigInteger(1, this.endAddress.getAddress());
+ BigInteger target = new BigInteger(1, address.getAddress());
+
+ int st = start.compareTo(target);
+ int te = target.compareTo(end);
+
+ return (st == -1 || st == 0) && (te == -1 || te == 0);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java b/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java
index 256453c..3d973f0 100644
--- a/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java
+++ b/src/main/java/com/ryanmichela/sshd/ConfigPasswordAuthenticator.java
@@ -9,11 +9,13 @@ import java.util.Map;
/**
* Copyright 2013 Ryan Michela
*/
-public class ConfigPasswordAuthenticator implements PasswordAuthenticator {
+public class ConfigPasswordAuthenticator extends IpFilteredAuthenticator implements PasswordAuthenticator {
private Map failCounts = new HashMap();
@Override
public boolean authenticate(String username, String password, ServerSession serverSession) {
+ if (!ipAddressIsApproved(serverSession)) return false;
+
if (SshdPlugin.instance.getConfig().getString("credentials." + username).equals(password)) {
failCounts.put(username, 0);
return true;
diff --git a/src/main/java/com/ryanmichela/sshd/IpFilteredAuthenticator.java b/src/main/java/com/ryanmichela/sshd/IpFilteredAuthenticator.java
new file mode 100644
index 0000000..859748b
--- /dev/null
+++ b/src/main/java/com/ryanmichela/sshd/IpFilteredAuthenticator.java
@@ -0,0 +1,28 @@
+package com.ryanmichela.sshd;
+
+import org.apache.sshd.server.session.ServerSession;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+
+/**
+ * Copyright 2014 Ryan Michela
+ */
+public class IpFilteredAuthenticator {
+ private NetworkAddressValidator addressValidator;
+
+ public IpFilteredAuthenticator() {
+ List whitelist = SshdPlugin.instance.getConfig().getStringList("whitelist");
+ if (whitelist.size() > 0) {
+ addressValidator = new NetworkAddressValidator(whitelist);
+ }
+ }
+
+ public boolean ipAddressIsApproved(ServerSession serverSession) {
+ if (addressValidator != null) {
+ String ip = ((InetSocketAddress)serverSession.getIoSession().getRemoteAddress()).getAddress().getHostAddress();
+ return addressValidator.isApproved(ip);
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/ryanmichela/sshd/NetworkAddressValidator.java b/src/main/java/com/ryanmichela/sshd/NetworkAddressValidator.java
new file mode 100644
index 0000000..15ca639
--- /dev/null
+++ b/src/main/java/com/ryanmichela/sshd/NetworkAddressValidator.java
@@ -0,0 +1,42 @@
+package com.ryanmichela.sshd;
+
+/**
+ * Copyright 2014 Ryan Michela
+ */
+
+import java.net.UnknownHostException;
+import java.util.List;
+
+public class NetworkAddressValidator {
+
+ private CIDRUtils[] approvedAddressList = null;
+
+ public NetworkAddressValidator(List approvedAddressList) {
+ this.approvedAddressList = new CIDRUtils[approvedAddressList.size()];
+ for (int i = 0; i < approvedAddressList.size(); i++) {
+ String whitelistEntry = approvedAddressList.get(i);
+ try {
+ if (approvedAddressList.get(i).indexOf("/") > 0) {
+ this.approvedAddressList[i] = new CIDRUtils(whitelistEntry);
+ } else {
+ this.approvedAddressList[i] = new CIDRUtils(whitelistEntry + "/32");
+ }
+ } catch (UnknownHostException e) {
+ SshdPlugin.instance.getLogger().severe(whitelistEntry + " is not a valid IPv4 or IPv6 address or CIDR formatted address.");
+ }
+ }
+ }
+
+ public boolean isApproved(String ipAddress) {
+ try {
+ for (CIDRUtils approvedAddress : approvedAddressList) {
+ if (approvedAddress.isInRange(ipAddress)) {
+ return true;
+ }
+ }
+ return false;
+ } catch (UnknownHostException e) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java b/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java
index ea61a74..312dd89 100644
--- a/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java
+++ b/src/main/java/com/ryanmichela/sshd/PublicKeyAuthenticator.java
@@ -11,7 +11,7 @@ import java.security.PublicKey;
/**
* Copyright 2013 Ryan Michela
*/
-public class PublicKeyAuthenticator implements PublickeyAuthenticator {
+public class PublicKeyAuthenticator extends IpFilteredAuthenticator implements PublickeyAuthenticator {
private File authorizedKeysDir;
public PublicKeyAuthenticator(File authorizedKeysDir) {
@@ -20,6 +20,8 @@ public class PublicKeyAuthenticator implements PublickeyAuthenticator {
@Override
public boolean authenticate(String username, PublicKey key, ServerSession session) {
+ if (!ipAddressIsApproved(session)) return false;
+
byte[] keyBytes = key.getEncoded();
File keyFile = new File(authorizedKeysDir, username);
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 1c29280..b41235f 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -9,4 +9,11 @@ port: 22
# For less secure username and password based authentication, complete the sections below.
credentials:
# user1: password1
-# user2: password2
\ No newline at end of file
+# user2: password2
+
+# To enable the IP whitelist, add more lines below. Whitelist entries can be expressed
+# in CIDR notation (ip address/mask) for whitelisting a range of IP addresses.
+whitelist:
+# - ::1/128
+# - 127.0.0.0/8
+# - 192.168.0.0/16
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index de1a15e..b6cc7f1 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,4 +1,4 @@
name: SSHD
-version: "1.1"
+version: "1.2"
author: Ryan Michela
main: com.ryanmichela.sshd.SshdPlugin
\ No newline at end of file