diff --git a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java index 7fb13bac6..9924eef8e 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -18,6 +18,7 @@ import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.util.InternetProtocolUtils; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.GameMode; import org.bukkit.Server; @@ -183,8 +184,7 @@ public class AsynchronousJoin implements AsynchronousProcess { private boolean validatePlayerCountForIp(final Player player, String ip) { if (service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP) > 0 && !service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) - && !"127.0.0.1".equalsIgnoreCase(ip) - && !"localhost".equalsIgnoreCase(ip) + && !InternetProtocolUtils.isLoopbackAddress(ip) && countOnlinePlayersByIp(ip) > service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP)) { bukkitService.scheduleSyncTaskFromOptionallyAsyncTask( diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index 22c46dd95..2441ea283 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -30,6 +30,7 @@ import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.util.InternetProtocolUtils; import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.Utils; import org.bukkit.ChatColor; @@ -325,8 +326,7 @@ public class AsynchronousLogin implements AsynchronousProcess { // Do not perform the check if player has multiple accounts permission or if IP is localhost if (service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP) <= 0 || service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) - || "127.0.0.1".equalsIgnoreCase(ip) - || "localhost".equalsIgnoreCase(ip)) { + || InternetProtocolUtils.isLoopbackAddress(ip)) { return false; } diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index fa5d03613..2655c6819 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -16,6 +16,7 @@ import fr.xephi.authme.service.bungeecord.BungeeSender; import fr.xephi.authme.service.bungeecord.MessageType; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.util.InternetProtocolUtils; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.entity.Player; @@ -119,8 +120,7 @@ public class AsyncRegister implements AsynchronousProcess { final int maxRegPerIp = service.getProperty(RestrictionSettings.MAX_REGISTRATION_PER_IP); final String ip = PlayerUtils.getPlayerIp(player); if (maxRegPerIp > 0 - && !"127.0.0.1".equalsIgnoreCase(ip) - && !"localhost".equalsIgnoreCase(ip) + && !InternetProtocolUtils.isLoopbackAddress(ip) && !service.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) { List otherAccounts = database.getAllAuthsByIp(ip); if (otherAccounts.size() >= maxRegPerIp) { diff --git a/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java b/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java index 548e1e913..039421548 100644 --- a/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java +++ b/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java @@ -1,16 +1,13 @@ package fr.xephi.authme.util; -import java.util.regex.Pattern; +import java.net.InetAddress; +import java.net.UnknownHostException; /** * Utility class about the InternetProtocol */ public final class InternetProtocolUtils { - private static final Pattern LOCAL_ADDRESS_PATTERN = - Pattern.compile("(^127\\.)|(^(0)?10\\.)|(^172\\.(0)?1[6-9]\\.)|(^172\\.(0)?2[0-9]\\.)" - + "|(^172\\.(0)?3[0-1]\\.)|(^169\\.254\\.)|(^192\\.168\\.)"); - // Utility class private InternetProtocolUtils() { } @@ -19,10 +16,57 @@ public final class InternetProtocolUtils { * Checks if the specified address is a private or loopback address * * @param address address to check - * - * @return true if the address is a local or loopback address, false otherwise + * @return true if the address is a local (site and link) or loopback address, false otherwise */ public static boolean isLocalAddress(String address) { - return LOCAL_ADDRESS_PATTERN.matcher(address).find(); + try { + InetAddress inetAddress = InetAddress.getByName(address); + + // Examples: 127.0.0.1, localhost or [::1] + return isLoopbackAddress(address) + // Example: 10.0.0.0, 172.16.0.0, 192.168.0.0, fec0::/10 (deprecated) + // Ref: https://en.wikipedia.org/wiki/IP_address#Private_addresses + || inetAddress.isSiteLocalAddress() + // Example: 169.254.0.0/16, fe80::/10 + // Ref: https://en.wikipedia.org/wiki/IP_address#Address_autoconfiguration + || inetAddress.isLinkLocalAddress() + // non deprecated unique site-local that java doesn't check yet -> fc00::/7 + || isIPv6UniqueSiteLocal(inetAddress); + } catch (UnknownHostException e) { + return false; + } + } + + /** + * Checks if the specified address is a loopback address. This can be one of the following: + * + * + * @param address address to check + * @return true if the address is a loopback one + */ + public static boolean isLoopbackAddress(String address) { + try { + InetAddress inetAddress = InetAddress.getByName(address); + return inetAddress.isLoopbackAddress(); + } catch (UnknownHostException e) { + return false; + } + } + + private static boolean isLoopbackAddress(InetAddress address) { + return address.isLoopbackAddress(); + } + + private static boolean isIPv6UniqueSiteLocal(InetAddress address) { + // ref: https://en.wikipedia.org/wiki/Unique_local_address + + // currently undefined but could be used in the near future fc00::/8 + return (address.getAddress()[0] & 0xFF) == 0xFC + // in use for unique site-local fd00::/8 + || (address.getAddress()[0] & 0xFF) == 0xFD; } } diff --git a/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java b/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java index f19810172..2c02e581f 100644 --- a/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java +++ b/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java @@ -124,7 +124,7 @@ public class AsynchronousLoginTest { public void shouldNotForceLoginUserWithAlreadyOnlineIp() { // given String name = "oscar"; - String ip = "127.0.12.245"; + String ip = "1.1.1.245"; Player player = mockPlayer(name); TestHelper.mockPlayerIp(player, ip); given(playerCache.isAuthenticated(name)).willReturn(false); @@ -147,7 +147,7 @@ public class AsynchronousLoginTest { public void shouldNotForceLoginForCanceledEvent() { // given String name = "oscar"; - String ip = "127.0.12.245"; + String ip = "1.1.1.245"; Player player = mockPlayer(name); TestHelper.mockPlayerIp(player, ip); given(playerCache.isAuthenticated(name)).willReturn(false); @@ -180,7 +180,7 @@ public class AsynchronousLoginTest { mockOnlinePlayersInBukkitService(); // when - boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "127.0.0.4"); + boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "1.1.1.1"); // then assertThat(result, equalTo(false)); @@ -195,7 +195,7 @@ public class AsynchronousLoginTest { given(commonService.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP)).willReturn(0); // when - boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "192.168.0.1"); + boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "2.2.2.2"); // then assertThat(result, equalTo(false)); @@ -210,7 +210,7 @@ public class AsynchronousLoginTest { given(commonService.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(true); // when - boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "127.0.0.4"); + boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "1.1.1.1"); // then assertThat(result, equalTo(false)); @@ -227,7 +227,7 @@ public class AsynchronousLoginTest { mockOnlinePlayersInBukkitService(); // when - boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "192.168.0.1"); + boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "2.2.2.2"); // then assertThat(result, equalTo(true)); @@ -242,28 +242,28 @@ public class AsynchronousLoginTest { } private void mockOnlinePlayersInBukkitService() { - // 127.0.0.4: albania (online), brazil (offline) + // 1.1.1.1: albania (online), brazil (offline) Player playerA = mockPlayer("albania"); - TestHelper.mockPlayerIp(playerA, "127.0.0.4"); + TestHelper.mockPlayerIp(playerA, "1.1.1.1"); given(dataSource.isLogged(playerA.getName())).willReturn(true); Player playerB = mockPlayer("brazil"); - TestHelper.mockPlayerIp(playerB, "127.0.0.4"); + TestHelper.mockPlayerIp(playerB, "1.1.1.1"); given(dataSource.isLogged(playerB.getName())).willReturn(false); - // 192.168.0.1: congo (online), denmark (offline), ecuador (online) + // 2.2.2.2: congo (online), denmark (offline), ecuador (online) Player playerC = mockPlayer("congo"); - TestHelper.mockPlayerIp(playerC, "192.168.0.1"); + TestHelper.mockPlayerIp(playerC, "2.2.2.2"); given(dataSource.isLogged(playerC.getName())).willReturn(true); Player playerD = mockPlayer("denmark"); - TestHelper.mockPlayerIp(playerD, "192.168.0.1"); + TestHelper.mockPlayerIp(playerD, "2.2.2.2"); given(dataSource.isLogged(playerD.getName())).willReturn(false); Player playerE = mockPlayer("ecuador"); - TestHelper.mockPlayerIp(playerE, "192.168.0.1"); + TestHelper.mockPlayerIp(playerE, "2.2.2.2"); given(dataSource.isLogged(playerE.getName())).willReturn(true); - // 192.168.0.0: france (offline) + // 3.3.3.3: france (offline) Player playerF = mockPlayer("france"); - TestHelper.mockPlayerIp(playerF, "192.168.0.0"); + TestHelper.mockPlayerIp(playerF, "3.3.3.3"); List onlinePlayers = Arrays.asList(playerA, playerB, playerC, playerD, playerE, playerF); returnGivenOnlinePlayers(bukkitService, onlinePlayers); diff --git a/src/test/java/fr/xephi/authme/util/InternetProtocolUtilsTest.java b/src/test/java/fr/xephi/authme/util/InternetProtocolUtilsTest.java index d45c0a578..02d8872a2 100644 --- a/src/test/java/fr/xephi/authme/util/InternetProtocolUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/InternetProtocolUtilsTest.java @@ -3,8 +3,8 @@ package fr.xephi.authme.util; import fr.xephi.authme.TestHelper; import org.junit.Test; -import static org.junit.Assert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; /** * Test for {@link InternetProtocolUtils} @@ -13,14 +13,44 @@ public class InternetProtocolUtilsTest { @Test public void shouldCheckLocalAddress() { + // loopback + assertThat(InternetProtocolUtils.isLocalAddress("localhost"), equalTo(true)); assertThat(InternetProtocolUtils.isLocalAddress("127.0.0.1"), equalTo(true)); + assertThat(InternetProtocolUtils.isLocalAddress("::1"), equalTo(true)); + + // site local assertThat(InternetProtocolUtils.isLocalAddress("10.0.0.1"), equalTo(true)); assertThat(InternetProtocolUtils.isLocalAddress("172.0.0.1"), equalTo(false)); assertThat(InternetProtocolUtils.isLocalAddress("172.16.0.1"), equalTo(true)); assertThat(InternetProtocolUtils.isLocalAddress("192.168.0.1"), equalTo(true)); + + // deprecated site-local + // ref: https://en.wikipedia.org/wiki/IPv6_address#Default_address_selection + assertThat(InternetProtocolUtils.isLocalAddress("fec0::"), equalTo(true)); + + // unique site-local (not deprecated!) + // ref: https://en.wikipedia.org/wiki/Unique_local_address + assertThat(InternetProtocolUtils.isLocalAddress("fde4:8dba:82e1::"), equalTo(true)); + assertThat(InternetProtocolUtils.isLocalAddress("fc00::"), equalTo(true)); + assertThat(InternetProtocolUtils.isLocalAddress("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), equalTo(true)); + assertThat(InternetProtocolUtils.isLocalAddress("fe00::"), equalTo(false)); + + // link local + assertThat(InternetProtocolUtils.isLocalAddress("169.254.0.64"), equalTo(true)); + assertThat(InternetProtocolUtils.isLocalAddress("FE80:0000:0000:0000:C800:0EFF:FE74:0008"), equalTo(true)); + + // public assertThat(InternetProtocolUtils.isLocalAddress("94.32.34.5"), equalTo(false)); } + @Test + public void testIsLoopback() { + // loopback + assertThat(InternetProtocolUtils.isLoopbackAddress("localhost"), equalTo(true)); + assertThat(InternetProtocolUtils.isLoopbackAddress("127.0.0.1"), equalTo(true)); + assertThat(InternetProtocolUtils.isLoopbackAddress("::1"), equalTo(true)); + } + @Test public void shouldHavePrivateConstructor() { // given / when / then