Ban ip, macs & hwid in PG

Finally rid of all db code in Client
This commit is contained in:
P0nk
2024-10-03 18:48:19 +02:00
parent 2b6ef9feb5
commit 40425ac4e1
10 changed files with 94 additions and 86 deletions

View File

@@ -52,14 +52,10 @@ import scripting.quest.QuestActionManager;
import scripting.quest.QuestScriptManager; import scripting.quest.QuestScriptManager;
import server.TimerManager; import server.TimerManager;
import server.life.Monster; import server.life.Monster;
import tools.DatabaseConnection;
import tools.PacketCreator; import tools.PacketCreator;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@@ -69,8 +65,6 @@ import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@@ -321,61 +315,6 @@ public class Client extends ChannelInboundHandlerAdapter {
return inServerTransition; return inServerTransition;
} }
// TODO: Recode to close statements...
// Only used from ban command.
private void loadMacsIfNescessary() throws SQLException {
if (macs.isEmpty()) {
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT macs FROM accounts WHERE id = ?")) {
ps.setInt(1, accId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
for (String mac : rs.getString("macs").split(", ")) {
if (!mac.equals("")) {
macs.add(mac);
}
}
}
}
}
}
}
public void banMacs() {
try {
loadMacsIfNescessary();
List<String> filtered = new LinkedList<>();
try (Connection con = DatabaseConnection.getConnection()) {
try (PreparedStatement ps = con.prepareStatement("SELECT filter FROM macfilters");
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
filtered.add(rs.getString("filter"));
}
}
try (PreparedStatement ps = con.prepareStatement("INSERT INTO macbans (mac, aid) VALUES (?, ?)")) {
for (String mac : macs) {
boolean matched = false;
for (String filter : filtered) {
if (mac.matches(filter)) {
matched = true;
break;
}
}
if (!matched) {
ps.setString(1, mac);
ps.setString(2, String.valueOf(getAccID()));
ps.executeUpdate();
}
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public void setPin(String pin) { public void setPin(String pin) {
this.pin = pin; this.pin = pin;
} }

View File

@@ -2,6 +2,7 @@ package database.account;
import client.LoginState; import client.LoginState;
import lombok.Builder; import lombok.Builder;
import net.server.coordinator.session.Hwid;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
@@ -14,7 +15,8 @@ import java.util.Objects;
@Builder @Builder
public record Account(int id, String name, String password, boolean acceptedTos, Byte gender, LocalDate birthdate, public record Account(int id, String name, String password, boolean acceptedTos, Byte gender, LocalDate birthdate,
String pin, String pic, byte chrSlots, LoginState loginState, LocalDateTime lastLogin, String pin, String pic, byte chrSlots, LoginState loginState, LocalDateTime lastLogin,
boolean banned, Instant bannedUntil, Byte banReason, String banDescription) { boolean banned, Instant bannedUntil, Byte banReason, String banDescription, String ip,
String macs, Hwid hwid) {
public Account { public Account {
Objects.requireNonNull(name); Objects.requireNonNull(name);
Objects.requireNonNull(password); Objects.requireNonNull(password);

View File

@@ -11,6 +11,10 @@ import java.util.Optional;
* @author Ponk * @author Ponk
*/ */
public class AccountRepository { public class AccountRepository {
private static final String SELECT_ACCOUNT_COLS = "a.id, a.name, password, pin, pic, birthdate, a.gender, " +
"tos_accepted, chr_slots, login_state, last_login, banned, banned_until, ban_reason, ban_description, " +
"ip, macs, hwid";
private final PgDatabaseConnection connection; private final PgDatabaseConnection connection;
public AccountRepository(PgDatabaseConnection connection) { public AccountRepository(PgDatabaseConnection connection) {
@@ -19,10 +23,9 @@ public class AccountRepository {
public Optional<Account> findByNameIgnoreCase(String name) { public Optional<Account> findByNameIgnoreCase(String name) {
String sql = """ String sql = """
SELECT id, name, password, pin, pic, birthdate, gender, tos_accepted, chr_slots, login_state, SELECT %s
last_login, banned, banned_until, ban_reason, ban_description FROM account AS a
FROM account WHERE lower(name) = lower(:name)""".formatted(SELECT_ACCOUNT_COLS);
WHERE lower(name) = lower(:name)""";
try (Handle handle = connection.getHandle()) { try (Handle handle = connection.getHandle()) {
return handle.createQuery(sql) return handle.createQuery(sql)
.bind("name", name) .bind("name", name)
@@ -33,10 +36,9 @@ public class AccountRepository {
public Optional<Account> findById(int accountId) { public Optional<Account> findById(int accountId) {
String sql = """ String sql = """
SELECT id, name, password, pin, pic, birthdate, gender, tos_accepted, chr_slots, login_state, SELECT %s
last_login, banned, banned_until, ban_reason, ban_description FROM account AS a
FROM account WHERE id = :id""".formatted(SELECT_ACCOUNT_COLS);
WHERE id = :id""";
try (Handle handle = connection.getHandle()) { try (Handle handle = connection.getHandle()) {
return handle.createQuery(sql) return handle.createQuery(sql)
.bind("id", accountId) .bind("id", accountId)
@@ -45,16 +47,16 @@ public class AccountRepository {
} }
} }
public Optional<Integer> findIdByChrNameIgnoreCase(String name) { public Optional<Account> findByChrNameIgnoreCase(String name) {
String sql = """ String sql = """
SELECT id SELECT %s
FROM account AS a FROM account AS a
INNER JOIN chr AS c ON a.id = c.account INNER JOIN chr AS c ON a.id = c.account
WHERE lower(c.name) = lower(:name)"""; WHERE lower(c.name) = lower(:name)""".formatted(SELECT_ACCOUNT_COLS);
try (Handle handle = connection.getHandle()) { try (Handle handle = connection.getHandle()) {
return handle.createQuery(sql) return handle.createQuery(sql)
.bind("name", name) .bind("name", name)
.mapTo(Integer.class) .mapTo(Account.class)
.findOne(); .findOne();
} }
} }
@@ -158,14 +160,14 @@ public class AccountRepository {
public boolean setBanned(int accountId, Instant bannedUntil, byte banReason, String description) { public boolean setBanned(int accountId, Instant bannedUntil, byte banReason, String description) {
String sql = """ String sql = """
UPDATE account UPDATE account
SET banned = true, banned_until = :bannedUntil, ban_reason = :banReason, description = :description SET banned = true, banned_until = :bannedUntil, ban_reason = :banReason, ban_description = :banDescription
WHERE id = :id"""; WHERE id = :id""";
try (Handle handle = connection.getHandle()) { try (Handle handle = connection.getHandle()) {
return handle.createUpdate(sql) return handle.createUpdate(sql)
.bind("id", accountId) .bind("id", accountId)
.bind("bannedUntil", bannedUntil) .bind("bannedUntil", bannedUntil)
.bind("banReason", banReason) .bind("banReason", banReason)
.bind("description", description) .bind("banDescription", description)
.execute() > 0; .execute() > 0;
} }
} }

View File

@@ -2,6 +2,7 @@ package database.account;
import client.LoginState; import client.LoginState;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.server.coordinator.session.Hwid;
import org.jdbi.v3.core.mapper.RowMapper; import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.statement.StatementContext; import org.jdbi.v3.core.statement.StatementContext;
@@ -40,6 +41,11 @@ public class AccountRowMapper implements RowMapper<Account> {
.orElse(null)) .orElse(null))
.banReason(rs.getByte("ban_reason")) .banReason(rs.getByte("ban_reason"))
.banDescription(rs.getString("ban_description")) .banDescription(rs.getString("ban_description"))
.ip(rs.getString("ip"))
.macs(rs.getString("macs"))
.hwid(Optional.ofNullable(rs.getString("hwid"))
.map(Hwid::new)
.orElse(null))
.build(); .build();
} }

View File

@@ -1,10 +1,12 @@
package database.ban; package database.ban;
import database.PgDatabaseConnection; import database.PgDatabaseConnection;
import lombok.extern.slf4j.Slf4j;
import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Handle;
import java.util.List; import java.util.List;
@Slf4j
public class HwidBanRepository { public class HwidBanRepository {
private final PgDatabaseConnection connection; private final PgDatabaseConnection connection;
@@ -22,4 +24,19 @@ public class HwidBanRepository {
.list(); .list();
} }
} }
public boolean saveHwidBan(String hwid, int accountId) {
String sql = """
INSERT INTO hwid_ban (hwid, account_id)
VALUES (:hwid, :accountId)""";
try (Handle handle = connection.getHandle()) {
return handle.createUpdate(sql)
.bind("hwid", hwid)
.bind("accountId", accountId)
.execute() > 0;
} catch (Exception e) {
log.error("Failed to save hwid ban. The hwid is already banned? accountId: {}, hwid: {}", accountId, hwid, e);
return false;
}
}
} }

View File

@@ -46,4 +46,11 @@ public class HwidBanManager {
public synchronized boolean isBanned(Hwid hwid) { public synchronized boolean isBanned(Hwid hwid) {
return bannedHwids.contains(hwid); return bannedHwids.contains(hwid);
} }
public synchronized void banHwid(Hwid hwid, int accountId) {
if (hwid == null) {
throw new IllegalArgumentException("hwid cannot be null");
}
hwidBanRepository.saveHwidBan(hwid.hwid(), accountId);
}
} }

View File

@@ -91,8 +91,8 @@ public class AccountService {
return accountRepository.findById(accountId); return accountRepository.findById(accountId);
} }
public Optional<Integer> getAccountIdByChrName(String chrName) { public Optional<Account> getAccountIdByChrName(String chrName) {
return accountRepository.findIdByChrNameIgnoreCase(chrName); return accountRepository.findByChrNameIgnoreCase(chrName);
} }
public boolean acceptTos(int accountId) { public boolean acceptTos(int accountId) {

View File

@@ -4,6 +4,7 @@ import client.Character;
import client.Client; import client.Client;
import client.autoban.AutobanFactory; import client.autoban.AutobanFactory;
import config.YamlConfig; import config.YamlConfig;
import database.account.Account;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.packet.Packet; import net.packet.Packet;
import net.server.Server; import net.server.Server;
@@ -16,6 +17,8 @@ import tools.PacketCreator;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -86,8 +89,6 @@ public class BanService {
ban(c, victimName, duration, reason, description); ban(c, victimName, duration, reason, description);
} }
// TODO: also ban ip and macs. Table "ipbans" and "macbans" (while taking "macfilters" into consideration).
// That's how it was done previously, anyway.
private void ban(Client c, String victimName, Duration duration, byte reason, String description) { private void ban(Client c, String victimName, Duration duration, byte reason, String description) {
Character victim = c.getChannelServer().getPlayerStorage().getCharacterByName(victimName); Character victim = c.getChannelServer().getPlayerStorage().getCharacterByName(victimName);
@@ -107,12 +108,16 @@ public class BanService {
} }
private boolean banOfflineChr(String victimName, Duration duration, byte reason, String description) { private boolean banOfflineChr(String victimName, Duration duration, byte reason, String description) {
Optional<Integer> foundAccountId = accountService.getAccountIdByChrName(victimName); Optional<Account> foundAccount = accountService.getAccountIdByChrName(victimName);
if (foundAccountId.isEmpty()) { if (foundAccount.isEmpty()) {
return false; return false;
} }
saveBan(foundAccountId.get(), duration, reason, description); Account account = foundAccount.get();
saveBan(account.id(), duration, reason, description);
banIp(account.ip(), account.id());
banMacs(account.macs(), account.id());
banHwid(account.hwid(), account.id());
return true; return true;
} }
@@ -122,8 +127,13 @@ public class BanService {
String ip = victim.getClient().getRemoteAddress(); String ip = victim.getClient().getRemoteAddress();
String enrichedDescription = "[%s] %s (IP: %s)".formatted(description, readableName, ip); String enrichedDescription = "[%s] %s (IP: %s)".formatted(description, readableName, ip);
saveBan(victim.getAccountID(), duration, reason, enrichedDescription); saveBan(victim.getAccountID(), duration, reason, enrichedDescription);
banIp(ip, victim.getAccountID());
Account victimAccount = victim.getClient().getAccount();
banMacs(victimAccount.macs(), victim.getAccountID());
banHwid(victimAccount.hwid(), victim.getAccountID());
victim.sendPacket(PacketCreator.sendPolice("You have been banned by %s.".formatted(c.getPlayer().getName()))); victim.sendPacket(PacketCreator.sendPolice("You have been banned by %s.".formatted(c.getPlayer().getName())));
TimerManager.getInstance().schedule(() -> transitionService.disconnect(c, false), TimerManager.getInstance().schedule(() -> transitionService.disconnect(victim.getClient(), true),
TimeUnit.SECONDS.toMillis(5)); TimeUnit.SECONDS.toMillis(5));
return true; return true;
} }
@@ -138,6 +148,31 @@ public class BanService {
accountService.ban(accountId, bannedUntil, reason, description); accountService.ban(accountId, bannedUntil, reason, description);
} }
private void banIp(String ip, int accountId) {
if (ip == null || ip.isEmpty()) {
return;
}
ipBanManager.banIp(ip, accountId);
}
private void banMacs(String macs, int accountId) {
if (macs == null || macs.isEmpty()) {
return;
}
List<String> macsToBan = Arrays.asList(macs.split(", "));
macsToBan.forEach(mac -> macBanManager.banMac(mac, accountId));
}
private void banHwid(Hwid hwid, int accountId) {
if (hwid == null) {
return;
}
hwidBanManager.banHwid(hwid, accountId);
}
public boolean isBanned(Client c) { public boolean isBanned(Client c) {
return isIpBanned(c) || isHwidBanned(c) || isMacBanned(c); return isIpBanned(c) || isHwidBanned(c) || isMacBanned(c);
} }

View File

@@ -174,7 +174,7 @@ public class TransitionService {
} }
} }
SessionCoordinator.getInstance().closeSession(c, false); SessionCoordinator.getInstance().closeSession(c, shutdown);
if (!c.isInTransition() && c.isLoggedIn()) { if (!c.isInTransition() && c.isLoggedIn()) {

View File

@@ -14,7 +14,7 @@ CREATE TABLE hwid_ban
created_at timestamp DEFAULT now() NOT NULL, created_at timestamp DEFAULT now() NOT NULL,
PRIMARY KEY (hwid) PRIMARY KEY (hwid)
); );
GRANT SELECT ON TABLE hwid_ban TO ${server-username}; GRANT SELECT, INSERT ON TABLE hwid_ban TO ${server-username};
CREATE TABLE mac_ban CREATE TABLE mac_ban
( (