package com.sk89q.commandbook.bans;
import au.com.bytecode.opencsv.CSVReader;
import au.com.bytecode.opencsv.CSVWriter;
import com.sk89q.commandbook.util.ChatUtil;
import com.sk89q.commandbook.util.ServerUtil;
import com.sk89q.commandbook.util.entity.player.UUIDUtil;
import org.apache.commons.lang.Validate;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.io.*;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.*;
import java.util.logging.Formatter;
import static com.sk89q.commandbook.CommandBook.logger;
public class CSVBanDatabase implements BanDatabase {
protected final Logger auditLogger
= Logger.getLogger("Minecraft.CommandBook.Bans");
protected final File storageFile;
/**
* Used to lookup bans by UUID
*/
protected Map<UUID, Ban> UUIDBan = new HashMap<UUID, Ban>();
/**
* Used to lookup bans by name
*/
@Deprecated
protected Map<String, Ban> nameBan = null;
/**
* Used to lookup bans by ip address
*/
protected Map<String, Ban> ipBan = new HashMap<String, Ban>();
/**
* A set of all bans. No ban in the lookup maps is not in here.
*/
protected final Set<Ban> bans = new HashSet<Ban>();
private static final SimpleDateFormat dateFormat =
new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
public CSVBanDatabase(File banStorageDir) {
storageFile = new File(banStorageDir, "bans.csv");
// Set up an audit trail
try {
FileHandler handler = new FileHandler(
(new File(banStorageDir, "bans.%g.%u.log")).getAbsolutePath()
.replace("\\", "/"), true);
handler.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
return "[" + dateFormat.format(new Date())
+ "] " + record.getMessage() + "\r\n";
}
});
auditLogger.addHandler(handler);
} catch (SecurityException e) {
logger().warning("Failed to setup audit log for the "
+ "CSV ban database: " + e.getMessage());
} catch (IOException e) {
logger().warning("Failed to setup audit log for the "
+ "CSV ban database: " + e.getMessage());
}
}
public synchronized boolean load() {
FileInputStream input = null;
boolean successful = true;
boolean needsSaved = false;
try {
input = new FileInputStream(storageFile);
InputStreamReader streamReader = new InputStreamReader(input, "utf-8");
CSVReader reader = new CSVReader(new BufferedReader(streamReader));
String[] line;
while ((line = reader.readNext()) != null) {
int lineLen = line.length;
if (lineLen < 5) {
logger().warning("A ban entry with < 5 fields was found!");
continue;
}
try {
int i = 0;
UUID ID = null;
if (lineLen > 5) {
String rawLine = line[i++];
if (!rawLine.isEmpty() && !rawLine.equals("null")) {
ID = UUID.fromString(rawLine);
}
}
String name = line[i++].toLowerCase();
String address = line[i++];
String reason = line[i++];
long startDate = Long.parseLong(line[i++]);
long endDate = Long.parseLong(line[i++]);
if ("".equals(name) || "null".equals(name)) name = null;
if ("".equals(address) || "null".equals(address)) address = null;
if ("".equals(reason) || "null".equals(reason)) reason = null;
Ban ban = new Ban(ID, name, address, reason, startDate, endDate);
if (ID != null) {
UUIDBan.put(ID, ban);
} else if (name != null) {
logger().finest("Converting " + name + "'s ban record to UUID...");
ID = UUIDUtil.convert(name);
if (ID != null) {
// Update the record
ban = new Ban(ID, name, address, reason, startDate, endDate);
UUIDBan.put(ID, ban);
// Log & request save
needsSaved = true;
logger().finest("Success!");
} else {
if (nameBan == null) {
nameBan = new HashMap<String, Ban>();
}
nameBan.put(name, ban);
logger().warning(ban.toString() + " could not be converted!");
}
}
if (address != null) ipBan.put(address, ban);
bans.add(ban);
} catch (IllegalArgumentException i) {
if (i instanceof NumberFormatException) {
logger().warning("Non-long long field found in ban!");
} else {
logger().warning("Invalid UUID field found in ban!");
}
}
}
logger().info(bans.size() + " banned name(s) loaded.");
} catch (FileNotFoundException ignored) {
} catch (IOException e) {
nameBan = new HashMap<String, Ban>();
ipBan = new HashMap<String, Ban>();
logger().warning("Failed to load " + storageFile.getAbsolutePath()
+ ": " + e.getMessage());
successful = false;
} finally {
if (input != null) {
try {
input.close();
} catch (IOException ignored) {
}
}
}
if (needsSaved) save();
return successful;
}
public synchronized boolean save() {
FileOutputStream output = null;
boolean successful = true;
try {
output = new FileOutputStream(storageFile);
CSVWriter writer = new CSVWriter(new BufferedWriter(new OutputStreamWriter(output, "utf-8")));
String[] line;
for (Ban ban : bans) {
line = new String[] {
String.valueOf(ban.getID()),
ban.getLastKnownAlias(),
ban.getAddress(),
ban.getReason(),
String.valueOf(ban.getStart()),
String.valueOf(ban.getEnd())
};
writer.writeNext(line);
}
writer.flush();
writer.close();
} catch (IOException e) {
logger().warning("Failed to save " + storageFile.getAbsolutePath()
+ ": " + e.getMessage());
successful = false;
} finally {
if (output != null) {
try {
output.close();
} catch (IOException ignored) {
}
}
}
return successful;
}
public boolean unload() {
for (Handler handler : auditLogger.getHandlers()) {
if (handler instanceof FileHandler) {
handler.flush();
handler.close();
auditLogger.removeHandler(handler);
return true;
}
}
return false;
}
@Override
public boolean isBanned(UUID ID) {
Ban ban = UUIDBan.get(ID);
if (ban != null) {
if (ban.getEnd() != 0L && ban.getEnd() - System.currentTimeMillis() <= 0) {
unban(ID, null, null, "Tempban expired");
save();
return false;
}
return true;
}
return false;
}
@Override
public boolean isBanned(InetAddress address) {
Ban ban = ipBan.get(address.getHostAddress());
if (ban != null) {
if (ban.getEnd() != 0L && ban.getEnd() - System.currentTimeMillis() <= 0) {
unban(null, address.getHostAddress(), null, "Tempban expired");
save();
return false;
}
return true;
}
return false;
}
@Override
public String getBannedMessage(UUID ID) {
Ban ban = UUIDBan.get(ID);
if (ban == null || ban.getReason() == null) return "You are banned.";
return ban.getReason();
}
@Override
public String getBannedMessage(String address) {
Ban ban = ipBan.get(address);
if (ban == null || ban.getReason() == null) return "You are banned by IP.";
return ban.getReason();
}
public void ban(Player player, CommandSender source, String reason, long end) {
ban(player.getUniqueId(), player.getName(), player.getAddress().getAddress().getHostAddress(), source, reason, end);
}
@Override
public void ban(UUID ID, String name, String address, CommandSender source, String reason, long end) {
Validate.isTrue(ID != null || address != null, "You must specify either an ID, or address");
Ban ban = new Ban(ID, name, address, reason, System.currentTimeMillis(), end);
if (ID != null) {
Ban oldBan = UUIDBan.remove(ID);
if (oldBan != null) {
bans.remove(oldBan);
}
UUIDBan.put(ID, ban);
}
if (address != null) {
Ban oldBan = ipBan.remove(address);
if (oldBan != null) {
bans.remove(oldBan);
}
ipBan.put(address, ban);
}
bans.add(ban);
auditLogger.info(String.format("BAN: %s (%s) added %s: %s",
source == null ? "Plugin" : ChatUtil.toUniqueName(source),
source == null ? "local" : ServerUtil.toInetAddressString(source),
ban.toString(),
reason));
}
@Override
public boolean unbanName(String name, CommandSender source, String reason) {
if (nameBan == null || name == null || name.isEmpty()) return false;
Ban ban = nameBan.remove(name.toLowerCase());
if (ban != null) {
bans.remove(ban);
auditLogger.info(String.format("UNBAN: %s (%s) removed %s: %s",
source == null ? "Plugin" : ChatUtil.toUniqueName(source),
source == null ? "local" : ServerUtil.toInetAddressString(source),
ban.toString(),
reason));
return true;
}
return false;
}
@Override
public boolean unban(Player player, CommandSender source, String reason) {
return unban(player.getUniqueId(), null, source, reason);
}
@Override
public boolean unban(UUID ID, String address, CommandSender source, String reason) {
Ban ban = null;
if (ID != null) {
ban = UUIDBan.remove(ID);
}
if (ban == null && address != null) {
ban = ipBan.remove(address);
}
if (ban != null) {
bans.remove(ban);
auditLogger.info(String.format("UNBAN: %s (%s) removed %s: %s",
source == null ? "Plugin" : ChatUtil.toUniqueName(source),
source == null ? "local" : ServerUtil.toInetAddressString(source),
ban.toString(),
reason));
return true;
}
return false;
}
public void logKick(Player player, CommandSender source, String reason) {
auditLogger.info(String.format("KICKED: %s (%s) kicked player '%s': %s",
ChatUtil.toUniqueName(source),
ServerUtil.toInetAddressString(source),
player.getName(),
reason));
}
public void importFrom(BanDatabase bans) {
for (Ban ban : bans) {
boolean set = false;
if (ban.getID() != null) {
set = true;
UUIDBan.put(ban.getID(), ban);
}
if (ban.getAddress() != null && !ban.getAddress().isEmpty()) {
set = true;
ipBan.put(ban.getAddress(), ban);
}
if (set) {
this.bans.add(ban);
} else {
logger().warning(ban.toString() + " could not be imported!");
}
}
}
@Override
public Ban getBanned(UUID ID) {
return UUIDBan.get(ID);
}
@Override
public Ban getBanned(String address) {
return ipBan.get(address);
}
public Iterator<Ban> iterator() {
return new Iterator<Ban>() {
private final Iterator<Ban> setIter = bans.iterator();
private Ban next;
public boolean hasNext() {
return setIter.hasNext();
}
public Ban next() {
return next = setIter.next();
}
public void remove() {
unban(next.getID(), next.getAddress(), null, "Removed by iterator");
}
};
}
}