From af02f8b7444defcffa5c458f8beb48948e09d4dd Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 1 Oct 2024 07:04:25 +0200 Subject: [PATCH] Redo hwid bans - reduce amount of db queries on login Works by loading all hwid bans on startup and querying the collection in memory rather than making calls on every login. --- src/main/java/client/Client.java | 28 ----------- src/main/java/database/JdbiConfig.java | 4 +- src/main/java/database/ban/HwidBan.java | 12 +++++ .../java/database/ban/HwidBanRepository.java | 25 ++++++++++ .../java/database/ban/HwidBanRowMapper.java | 18 +++++++ src/main/java/net/ChannelDependencies.java | 5 +- src/main/java/net/PacketProcessor.java | 16 +++--- src/main/java/net/server/Server.java | 7 ++- .../handlers/login/CharSelectedHandler.java | 8 ++- .../login/CharSelectedWithPicHandler.java | 8 ++- .../handlers/login/LoginPasswordHandler.java | 2 +- .../handlers/login/RegisterPicHandler.java | 8 ++- .../login/ViewAllCharRegisterPicHandler.java | 9 +++- .../login/ViewAllCharSelectedHandler.java | 8 ++- .../ViewAllCharSelectedWithPicHandler.java | 8 ++- src/main/java/server/ban/HwidBanManager.java | 49 +++++++++++++++++++ src/main/java/service/BanService.java | 14 +++++- .../db/migration/postgresql/V0.10__ban.sql | 9 ++++ .../db/migration/postgresql/V0.2__account.sql | 4 +- .../java/server/ban/HwidBanManagerTest.java | 48 ++++++++++++++++++ 20 files changed, 235 insertions(+), 55 deletions(-) create mode 100644 src/main/java/database/ban/HwidBan.java create mode 100644 src/main/java/database/ban/HwidBanRepository.java create mode 100644 src/main/java/database/ban/HwidBanRowMapper.java create mode 100644 src/main/java/server/ban/HwidBanManager.java create mode 100644 src/test/java/server/ban/HwidBanManagerTest.java diff --git a/src/main/java/client/Client.java b/src/main/java/client/Client.java index 14f52c8ebc..3eb77a3eba 100644 --- a/src/main/java/client/Client.java +++ b/src/main/java/client/Client.java @@ -315,32 +315,6 @@ public class Client extends ChannelInboundHandlerAdapter { return inServerTransition; } - // TODO: load hwidbans on server start and query it on demand. This query should not be run on every login! - @Deprecated - public boolean hasBannedHWID() { - if (hwid == null) { - return false; - } - - boolean ret = false; - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM hwidbans WHERE hwid LIKE ?")) { - ps.setString(1, hwid.hwid()); - - try (ResultSet rs = ps.executeQuery()) { - if (rs != null && rs.next()) { - if (rs.getInt(1) > 0) { - ret = true; - } - } - } - } catch (SQLException e) { - e.printStackTrace(); - } - - return ret; - } - // TODO: load macbans on server start and query it on demand. This query should not be run on every login! @Deprecated public boolean hasBannedMac() { @@ -490,8 +464,6 @@ public class Client extends ChannelInboundHandlerAdapter { } public void updateHwid(Hwid hwid) { - this.hwid = hwid; - try (Connection con = DatabaseConnection.getConnection(); PreparedStatement ps = con.prepareStatement("UPDATE accounts SET hwid = ? WHERE id = ?")) { ps.setString(1, hwid.hwid()); diff --git a/src/main/java/database/JdbiConfig.java b/src/main/java/database/JdbiConfig.java index ed1b0edf85..06804a9273 100644 --- a/src/main/java/database/JdbiConfig.java +++ b/src/main/java/database/JdbiConfig.java @@ -1,6 +1,7 @@ package database; import database.account.AccountRowMapper; +import database.ban.HwidBanRowMapper; import database.ban.IpBanRowMapper; import database.drop.GlobalMonsterDropRowMapper; import database.drop.MonsterDropRowMapper; @@ -38,7 +39,8 @@ public final class JdbiConfig { new ShopRowMapper(), new ShopItemRowMapper(), new MonsterCardRowMapper(), - new IpBanRowMapper() + new IpBanRowMapper(), + new HwidBanRowMapper() ); } } diff --git a/src/main/java/database/ban/HwidBan.java b/src/main/java/database/ban/HwidBan.java new file mode 100644 index 0000000000..ac49e5c9c6 --- /dev/null +++ b/src/main/java/database/ban/HwidBan.java @@ -0,0 +1,12 @@ +package database.ban; + +import lombok.Builder; + +import java.util.Objects; + +@Builder +public record HwidBan(String hwid, Integer accountId) { + public HwidBan { + Objects.requireNonNull(hwid); + } +} diff --git a/src/main/java/database/ban/HwidBanRepository.java b/src/main/java/database/ban/HwidBanRepository.java new file mode 100644 index 0000000000..6978bd0219 --- /dev/null +++ b/src/main/java/database/ban/HwidBanRepository.java @@ -0,0 +1,25 @@ +package database.ban; + +import database.PgDatabaseConnection; +import org.jdbi.v3.core.Handle; + +import java.util.List; + +public class HwidBanRepository { + private final PgDatabaseConnection connection; + + public HwidBanRepository(PgDatabaseConnection connection) { + this.connection = connection; + } + + public List getAllHwidBans() { + String sql = """ + SELECT hwid, account_id + FROM hwid_ban"""; + try (Handle handle = connection.getHandle()) { + return handle.createQuery(sql) + .mapTo(HwidBan.class) + .list(); + } + } +} diff --git a/src/main/java/database/ban/HwidBanRowMapper.java b/src/main/java/database/ban/HwidBanRowMapper.java new file mode 100644 index 0000000000..56cffd3ce3 --- /dev/null +++ b/src/main/java/database/ban/HwidBanRowMapper.java @@ -0,0 +1,18 @@ +package database.ban; + +import org.jdbi.v3.core.mapper.RowMapper; +import org.jdbi.v3.core.statement.StatementContext; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class HwidBanRowMapper implements RowMapper { + + @Override + public HwidBan map(ResultSet rs, StatementContext ctx) throws SQLException { + return HwidBan.builder() + .hwid(rs.getString("hwid")) + .accountId(rs.getObject("account_id", Integer.class)) + .build(); + } +} diff --git a/src/main/java/net/ChannelDependencies.java b/src/main/java/net/ChannelDependencies.java index 21d843e864..36a9589d65 100644 --- a/src/main/java/net/ChannelDependencies.java +++ b/src/main/java/net/ChannelDependencies.java @@ -8,6 +8,7 @@ import database.character.CharacterLoader; import database.character.CharacterSaver; import database.drop.DropProvider; import lombok.Builder; +import server.ban.HwidBanManager; import server.ban.IpBanManager; import server.shop.ShopFactory; import service.AccountService; @@ -26,7 +27,8 @@ public record ChannelDependencies( CharacterCreator characterCreator, CharacterLoader characterLoader, CharacterSaver characterSaver, NoteService noteService, FredrickProcessor fredrickProcessor, MakerProcessor makerProcessor, DropProvider dropProvider, CommandsExecutor commandsExecutor, ShopFactory shopFactory, - TransitionService transitionService, IpBanManager ipBanManager, BanService banService + TransitionService transitionService, IpBanManager ipBanManager, HwidBanManager hwidBanManager, + BanService banService ) { public ChannelDependencies { @@ -42,6 +44,7 @@ public record ChannelDependencies( Objects.requireNonNull(shopFactory); Objects.requireNonNull(transitionService); Objects.requireNonNull(ipBanManager); + Objects.requireNonNull(hwidBanManager); Objects.requireNonNull(banService); } } diff --git a/src/main/java/net/PacketProcessor.java b/src/main/java/net/PacketProcessor.java index e2b66e5adf..2367de85c7 100644 --- a/src/main/java/net/PacketProcessor.java +++ b/src/main/java/net/PacketProcessor.java @@ -280,7 +280,8 @@ public final class PacketProcessor { registerHandler(RecvOpcode.AFTER_LOGIN, new AfterLoginHandler(channelDeps.accountService())); registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler()); registerHandler(RecvOpcode.CHARLIST_REQUEST, new CharlistRequestHandler()); - registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler(channelDeps.transitionService())); + registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler(channelDeps.banService(), + channelDeps.transitionService())); registerHandler(RecvOpcode.LOGIN_PASSWORD, new LoginPasswordHandler(channelDeps.accountService(), channelDeps.transitionService(), channelDeps.banService())); registerHandler(RecvOpcode.RELOG, new RelogRequestHandler()); @@ -290,17 +291,18 @@ public final class PacketProcessor { registerHandler(RecvOpcode.CREATE_CHAR, new CreateCharHandler(channelDeps.characterCreator())); registerHandler(RecvOpcode.DELETE_CHAR, new DeleteCharHandler()); registerHandler(RecvOpcode.VIEW_ALL_CHAR, new ViewAllCharHandler()); - registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler(channelDeps.transitionService())); + registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler(channelDeps.banService(), + channelDeps.transitionService())); registerHandler(RecvOpcode.REGISTER_PIN, new RegisterPinHandler(channelDeps.accountService())); registerHandler(RecvOpcode.GUEST_LOGIN, new GuestLoginHandler()); - registerHandler(RecvOpcode.REGISTER_PIC, new RegisterPicHandler(channelDeps.accountService(), - channelDeps.transitionService())); - registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler( + registerHandler(RecvOpcode.REGISTER_PIC, new RegisterPicHandler(channelDeps.banService(), + channelDeps.accountService(), channelDeps.transitionService())); + registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler(channelDeps.banService(), channelDeps.transitionService())); registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler(channelDeps.accountService())); - registerHandler(RecvOpcode.VIEW_ALL_WITH_PIC, new ViewAllCharSelectedWithPicHandler( + registerHandler(RecvOpcode.VIEW_ALL_WITH_PIC, new ViewAllCharSelectedWithPicHandler(channelDeps.banService(), channelDeps.transitionService())); - registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllCharRegisterPicHandler( + registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllCharRegisterPicHandler(channelDeps.banService(), channelDeps.accountService(), channelDeps.transitionService())); } diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index 64888de6db..68d3cb52ad 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -45,6 +45,7 @@ import constants.net.ServerConstants; import database.PgDatabaseConfig; import database.PgDatabaseConnection; import database.account.AccountRepository; +import database.ban.HwidBanRepository; import database.ban.IpBanRepository; import database.character.CharacterLoader; import database.character.CharacterRepository; @@ -86,6 +87,7 @@ import server.CashShop.CashItemFactory; import server.SkillbookInformationProvider; import server.ThreadManager; import server.TimerManager; +import server.ban.HwidBanManager; import server.ban.IpBanManager; import server.expeditions.ExpeditionBossLog; import server.life.PlayerNPC; @@ -719,6 +721,7 @@ public class Server { futures.add(initExecutor.submit(Quest::loadAllQuests)); futures.add(initExecutor.submit(SkillbookInformationProvider::loadAllSkillbookInformation)); futures.add(initExecutor.submit(channelDependencies.ipBanManager()::loadIpBans)); + futures.add(initExecutor.submit(channelDependencies.hwidBanManager()::loadHwidBans)); initExecutor.shutdown(); TimeZone.setDefault(TimeZone.getTimeZone(YamlConfig.config.server.TIMEZONE)); @@ -833,7 +836,8 @@ public class Server { DropProvider dropProvider = new DropProvider(new DropRepository(connection)); ShopFactory shopFactory = new ShopFactory(new ShopDao(connection)); IpBanManager ipBanManager = new IpBanManager(new IpBanRepository(connection)); - BanService banService = new BanService(accountService, transitionService, ipBanManager); + HwidBanManager hwidBanManager = new HwidBanManager(new HwidBanRepository(connection)); + BanService banService = new BanService(accountService, transitionService, ipBanManager, hwidBanManager); ChannelDependencies channelDependencies = ChannelDependencies.builder() .accountService(accountService) .characterCreator(new CharacterCreator(connection, characterRepository)) @@ -848,6 +852,7 @@ public class Server { .shopFactory(shopFactory) .transitionService(transitionService) .ipBanManager(ipBanManager) + .hwidBanManager(hwidBanManager) .banService(banService) .build(); diff --git a/src/main/java/net/server/handlers/login/CharSelectedHandler.java b/src/main/java/net/server/handlers/login/CharSelectedHandler.java index 02a86c75ac..969dbd77fb 100644 --- a/src/main/java/net/server/handlers/login/CharSelectedHandler.java +++ b/src/main/java/net/server/handlers/login/CharSelectedHandler.java @@ -31,6 +31,7 @@ import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult; import net.server.world.World; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import service.BanService; import service.TransitionService; import tools.PacketCreator; @@ -40,9 +41,11 @@ import java.net.UnknownHostException; public final class CharSelectedHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(CharSelectedHandler.class); + private final BanService banService; private final TransitionService transitionService; - public CharSelectedHandler(TransitionService transitionService) { + public CharSelectedHandler(BanService banService, TransitionService transitionService) { + this.banService = banService; this.transitionService = transitionService; } @@ -74,6 +77,7 @@ public final class CharSelectedHandler extends AbstractPacketHandler { c.updateMacs(macs); c.updateHwid(hwid); + c.setHwid(hwid); AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid); if (res != AntiMulticlientResult.SUCCESS) { @@ -81,7 +85,7 @@ public final class CharSelectedHandler extends AbstractPacketHandler { return; } - if (c.hasBannedMac() || c.hasBannedHWID()) { + if (banService.isBanned(c) || c.hasBannedMac()) { SessionCoordinator.getInstance().closeSession(c, true); return; } diff --git a/src/main/java/net/server/handlers/login/CharSelectedWithPicHandler.java b/src/main/java/net/server/handlers/login/CharSelectedWithPicHandler.java index 1c6f9a7501..6db9738820 100644 --- a/src/main/java/net/server/handlers/login/CharSelectedWithPicHandler.java +++ b/src/main/java/net/server/handlers/login/CharSelectedWithPicHandler.java @@ -10,6 +10,7 @@ import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult; import net.server.world.World; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import service.BanService; import service.TransitionService; import tools.PacketCreator; @@ -19,9 +20,11 @@ import java.net.UnknownHostException; public class CharSelectedWithPicHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(CharSelectedWithPicHandler.class); + private final BanService banService; private final TransitionService transitionService; - public CharSelectedWithPicHandler(TransitionService transitionService) { + public CharSelectedWithPicHandler(BanService banService, TransitionService transitionService) { + this.banService = banService; this.transitionService = transitionService; } @@ -54,8 +57,9 @@ public class CharSelectedWithPicHandler extends AbstractPacketHandler { c.updateMacs(macs); c.updateHwid(hwid); + c.setHwid(hwid); - if (c.hasBannedMac() || c.hasBannedHWID()) { + if (banService.isBanned(c) || c.hasBannedMac()) { SessionCoordinator.getInstance().closeSession(c, true); return; } diff --git a/src/main/java/net/server/handlers/login/LoginPasswordHandler.java b/src/main/java/net/server/handlers/login/LoginPasswordHandler.java index c198bc1a24..63dbfadd46 100644 --- a/src/main/java/net/server/handlers/login/LoginPasswordHandler.java +++ b/src/main/java/net/server/handlers/login/LoginPasswordHandler.java @@ -114,7 +114,7 @@ public final class LoginPasswordHandler implements PacketHandler { } boolean banCheckDisabled = false; - if (!banCheckDisabled && (banService.isBanned(c) || c.hasBannedMac() || c.hasBannedHWID())) { + if (!banCheckDisabled && (banService.isBanned(c) || c.hasBannedMac())) { c.sendPacket(PacketCreator.getLoginFailed(3)); return; } diff --git a/src/main/java/net/server/handlers/login/RegisterPicHandler.java b/src/main/java/net/server/handlers/login/RegisterPicHandler.java index ef01d6dda6..0209369e76 100644 --- a/src/main/java/net/server/handlers/login/RegisterPicHandler.java +++ b/src/main/java/net/server/handlers/login/RegisterPicHandler.java @@ -11,6 +11,7 @@ import net.server.world.World; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import service.AccountService; +import service.BanService; import service.TransitionService; import tools.PacketCreator; @@ -20,10 +21,12 @@ import java.net.UnknownHostException; public final class RegisterPicHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(RegisterPicHandler.class); + private final BanService banService; private final TransitionService transitionService; private final AccountService accountService; - public RegisterPicHandler(AccountService accountService, TransitionService transitionService) { + public RegisterPicHandler(BanService banService, AccountService accountService, TransitionService transitionService) { + this.banService = banService; this.accountService = accountService; this.transitionService = transitionService; } @@ -47,6 +50,7 @@ public final class RegisterPicHandler extends AbstractPacketHandler { c.updateMacs(macs); c.updateHwid(hwid); + c.setHwid(hwid); AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid); if (res != AntiMulticlientResult.SUCCESS) { @@ -54,7 +58,7 @@ public final class RegisterPicHandler extends AbstractPacketHandler { return; } - if (c.hasBannedMac() || c.hasBannedHWID()) { + if (banService.isBanned(c) || c.hasBannedMac()) { SessionCoordinator.getInstance().closeSession(c, true); return; } diff --git a/src/main/java/net/server/handlers/login/ViewAllCharRegisterPicHandler.java b/src/main/java/net/server/handlers/login/ViewAllCharRegisterPicHandler.java index 92322146ca..72755cdc18 100644 --- a/src/main/java/net/server/handlers/login/ViewAllCharRegisterPicHandler.java +++ b/src/main/java/net/server/handlers/login/ViewAllCharRegisterPicHandler.java @@ -11,6 +11,7 @@ import net.server.world.World; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import service.AccountService; +import service.BanService; import service.TransitionService; import tools.PacketCreator; import tools.Randomizer; @@ -21,10 +22,13 @@ import java.net.UnknownHostException; public final class ViewAllCharRegisterPicHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(ViewAllCharRegisterPicHandler.class); + private final BanService banService; private final AccountService accountService; private final TransitionService transitionService; - public ViewAllCharRegisterPicHandler(AccountService accountService, TransitionService transitionService) { + public ViewAllCharRegisterPicHandler(BanService banService, AccountService accountService, + TransitionService transitionService) { + this.banService = banService; this.accountService = accountService; this.transitionService = transitionService; } @@ -49,8 +53,9 @@ public final class ViewAllCharRegisterPicHandler extends AbstractPacketHandler { c.updateMacs(mac); c.updateHwid(hwid); + c.setHwid(hwid); - if (c.hasBannedMac() || c.hasBannedHWID()) { + if (banService.isBanned(c) || c.hasBannedMac()) { SessionCoordinator.getInstance().closeSession(c, true); return; } diff --git a/src/main/java/net/server/handlers/login/ViewAllCharSelectedHandler.java b/src/main/java/net/server/handlers/login/ViewAllCharSelectedHandler.java index fbf6c8cd70..1681878960 100644 --- a/src/main/java/net/server/handlers/login/ViewAllCharSelectedHandler.java +++ b/src/main/java/net/server/handlers/login/ViewAllCharSelectedHandler.java @@ -31,6 +31,7 @@ import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult; import net.server.world.World; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import service.BanService; import service.TransitionService; import tools.PacketCreator; import tools.Randomizer; @@ -41,9 +42,11 @@ import java.net.UnknownHostException; public final class ViewAllCharSelectedHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(ViewAllCharSelectedHandler.class); + private final BanService banService; private final TransitionService transitionService; - public ViewAllCharSelectedHandler(TransitionService transitionService) { + public ViewAllCharSelectedHandler(BanService banService, TransitionService transitionService) { + this.banService = banService; this.transitionService = transitionService; } @@ -76,8 +79,9 @@ public final class ViewAllCharSelectedHandler extends AbstractPacketHandler { c.updateMacs(macs); c.updateHwid(hwid); + c.setHwid(hwid); - if (c.hasBannedMac() || c.hasBannedHWID()) { + if (banService.isBanned(c) || c.hasBannedMac()) { SessionCoordinator.getInstance().closeSession(c, true); return; } diff --git a/src/main/java/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java b/src/main/java/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java index 9364a6e5f3..3a42eb572e 100644 --- a/src/main/java/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java +++ b/src/main/java/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java @@ -10,6 +10,7 @@ import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult; import net.server.world.World; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import service.BanService; import service.TransitionService; import tools.PacketCreator; import tools.Randomizer; @@ -20,9 +21,11 @@ import java.net.UnknownHostException; public class ViewAllCharSelectedWithPicHandler extends AbstractPacketHandler { private static final Logger log = LoggerFactory.getLogger(ViewAllCharSelectedWithPicHandler.class); + private final BanService banService; private final TransitionService transitionService; - public ViewAllCharSelectedWithPicHandler(TransitionService transitionService) { + public ViewAllCharSelectedWithPicHandler(BanService banService, TransitionService transitionService) { + this.banService = banService; this.transitionService = transitionService; } @@ -57,8 +60,9 @@ public class ViewAllCharSelectedWithPicHandler extends AbstractPacketHandler { c.updateMacs(macs); c.updateHwid(hwid); + c.setHwid(hwid); - if (c.hasBannedMac() || c.hasBannedHWID()) { + if (banService.isBanned(c) || c.hasBannedMac()) { SessionCoordinator.getInstance().closeSession(c, true); return; } diff --git a/src/main/java/server/ban/HwidBanManager.java b/src/main/java/server/ban/HwidBanManager.java new file mode 100644 index 0000000000..13c8029bc0 --- /dev/null +++ b/src/main/java/server/ban/HwidBanManager.java @@ -0,0 +1,49 @@ +package server.ban; + +import database.ban.HwidBan; +import database.ban.HwidBanRepository; +import lombok.extern.slf4j.Slf4j; +import net.jcip.annotations.ThreadSafe; +import net.server.coordinator.session.Hwid; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * @author Ponk + */ +@ThreadSafe +@Slf4j +public class HwidBanManager { + private final HwidBanRepository hwidBanRepository; + private final Set bannedHwids = new HashSet<>(); + + public HwidBanManager(HwidBanRepository hwidBanRepository) { + this.hwidBanRepository = hwidBanRepository; + } + + public synchronized void loadHwidBans() { + List hwidBans = hwidBanRepository.getAllHwidBans(); + log.debug("Loaded {} hwid bans", hwidBans.size()); + bannedHwids.addAll(hwidBans.stream() + .map(HwidBanManager::createHwid) + .filter(Objects::nonNull) + .toList() + ); + } + + private static Hwid createHwid(HwidBan hwidBan) { + try { + return new Hwid(hwidBan.hwid()); + } catch (IllegalArgumentException e) { + log.warn("Unable to create Hwid from: {} due to bad 'hwid' value in database", hwidBan); + return null; + } + } + + public synchronized boolean isBanned(Hwid hwid) { + return bannedHwids.contains(hwid); + } +} diff --git a/src/main/java/service/BanService.java b/src/main/java/service/BanService.java index ab839c5c64..6b7972ea19 100644 --- a/src/main/java/service/BanService.java +++ b/src/main/java/service/BanService.java @@ -7,7 +7,9 @@ import config.YamlConfig; import lombok.extern.slf4j.Slf4j; import net.packet.Packet; import net.server.Server; +import net.server.coordinator.session.Hwid; import server.TimerManager; +import server.ban.HwidBanManager; import server.ban.IpBanManager; import tools.PacketCreator; @@ -21,11 +23,14 @@ public class BanService { private final AccountService accountService; private final TransitionService transitionService; private final IpBanManager ipBanManager; + private final HwidBanManager hwidBanManager; - public BanService(AccountService accountService, TransitionService transitionService, IpBanManager ipBanManager) { + public BanService(AccountService accountService, TransitionService transitionService, IpBanManager ipBanManager, + HwidBanManager hwidBanManager) { this.accountService = accountService; this.transitionService = transitionService; this.ipBanManager = ipBanManager; + this.hwidBanManager = hwidBanManager; } public void autoban(Character chr, AutobanFactory type, String reason) { @@ -116,11 +121,16 @@ public class BanService { } public boolean isBanned(Client c) { - return isIpBanned(c); + return isIpBanned(c) || isHwidBanned(c); } private boolean isIpBanned(Client c) { String ip = c.getRemoteAddress(); return ip != null && ipBanManager.isBanned(ip); } + + private boolean isHwidBanned(Client c) { + Hwid hwid = c.getHwid(); + return hwid != null && hwidBanManager.isBanned(hwid); + } } diff --git a/src/main/resources/db/migration/postgresql/V0.10__ban.sql b/src/main/resources/db/migration/postgresql/V0.10__ban.sql index 437942a82d..e9ab53e2c7 100644 --- a/src/main/resources/db/migration/postgresql/V0.10__ban.sql +++ b/src/main/resources/db/migration/postgresql/V0.10__ban.sql @@ -6,3 +6,12 @@ CREATE TABLE ip_ban PRIMARY KEY (ip) ); GRANT SELECT, INSERT ON TABLE ip_ban TO ${server-username}; + +CREATE TABLE hwid_ban +( + hwid varchar(30) NOT NULL, + account_id integer, + created_at timestamp DEFAULT now() NOT NULL, + PRIMARY KEY (hwid) +); +GRANT SELECT ON TABLE hwid_ban TO ${server-username}; diff --git a/src/main/resources/db/migration/postgresql/V0.2__account.sql b/src/main/resources/db/migration/postgresql/V0.2__account.sql index f7273ba2df..4fc74fdec8 100644 --- a/src/main/resources/db/migration/postgresql/V0.2__account.sql +++ b/src/main/resources/db/migration/postgresql/V0.2__account.sql @@ -30,5 +30,5 @@ GRANT SELECT, INSERT, UPDATE ON TABLE account TO ${server-username}; GRANT USAGE ON SEQUENCE account_id_seq TO ${server-username}; ALTER SEQUENCE account_id_seq RESTART WITH 1000; --- INSERT INTO account (id, name, password, pin, pic, birthdate) --- VALUES (1, 'admin', '$2y$12$aFD9BDeUocDMY1X4tDYDyeJw/HhkQwCQWs3KAY7gCaRG0cpqJcaL.', '0000', '000000', '2005-05-11'); +INSERT INTO account (id, name, password, pin, pic, birthdate, tos_accepted, chr_slots, login_state) +VALUES (nextval('account_id_seq'), 'admin', '$2y$12$aFD9BDeUocDMY1X4tDYDyeJw/HhkQwCQWs3KAY7gCaRG0cpqJcaL.', '0000', '000000', '2005-05-11', true, 9, 0); diff --git a/src/test/java/server/ban/HwidBanManagerTest.java b/src/test/java/server/ban/HwidBanManagerTest.java new file mode 100644 index 0000000000..3c3c654ecc --- /dev/null +++ b/src/test/java/server/ban/HwidBanManagerTest.java @@ -0,0 +1,48 @@ +package server.ban; + +import database.DatabaseTest; +import database.ban.HwidBanRepository; +import net.server.coordinator.session.Hwid; +import org.jdbi.v3.core.Handle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class HwidBanManagerTest extends DatabaseTest { + private HwidBanRepository hwidBanRepository; + private HwidBanManager hwidBanManager; + + @BeforeEach + void setUp() { + this.hwidBanRepository = new HwidBanRepository(connection); + this.hwidBanManager = new HwidBanManager(hwidBanRepository); + } + + @AfterEach + void deleteHwidBans() { + clearTable("hwid_ban"); + } + + @Test + void loadHwidBans_shouldLoadFromRepository() { + Hwid hwid = new Hwid("ABC1DEF2"); + assertFalse(hwidBanManager.isBanned(hwid)); + + hwidBanManager.loadHwidBans(); + assertFalse(hwidBanManager.isBanned(hwid)); + + saveHwidBan(hwid); + hwidBanManager.loadHwidBans(); + + assertTrue(hwidBanManager.isBanned(hwid)); + } + + private void saveHwidBan(Hwid hwid) { + try (Handle handle = connection.getHandle()) { + handle.execute("INSERT INTO hwid_ban (hwid) VALUES (?)", hwid.hwid()); + } + } +}