Implement authentication

This commit is contained in:
ayunami2000 2022-07-23 20:06:46 -04:00
parent c21abb5dfd
commit 28f4a9c406
12 changed files with 468 additions and 89 deletions

View file

@ -62,6 +62,7 @@ import net.md_5.bungee.api.scheduler.TaskScheduler;
import net.md_5.bungee.api.tab.CustomTabList;
import net.md_5.bungee.command.CommandAlert;
import net.md_5.bungee.command.CommandBungee;
import net.md_5.bungee.command.CommandChangePassword;
import net.md_5.bungee.command.CommandClearRatelimit;
import net.md_5.bungee.command.CommandConfirmCode;
import net.md_5.bungee.command.CommandDomain;
@ -87,6 +88,7 @@ import net.md_5.bungee.command.CommandServer;
import net.md_5.bungee.command.ConsoleCommandSender;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.YamlConfig;
import net.md_5.bungee.eaglercraft.AuthSystem;
import net.md_5.bungee.eaglercraft.BanList;
import net.md_5.bungee.eaglercraft.DomainBlacklist;
import net.md_5.bungee.eaglercraft.PluginEaglerSkins;
@ -126,6 +128,7 @@ public class BungeeCord extends ProxyServer {
private ConsoleReader consoleReader;
private final Logger logger;
private Collection<Command> banCommands;
public AuthSystem authSystem;
public static BungeeCord getInstance() {
return (BungeeCord) ProxyServer.getInstance();
@ -234,7 +237,10 @@ public class BungeeCord extends ProxyServer {
this.pluginManager.detectPlugins(this.pluginsFolder);
this.pluginManager.addInternalPlugin(new PluginEaglerSkins());
this.pluginManager.addInternalPlugin(new PluginEaglerVoice(this.config.getVoiceEnabled()));
//if(this.config.getAuthInfo().isEnabled()) this.pluginManager.addInternalPlugin(new PluginEaglerAuth());
if (this.config.getAuthInfo().isEnabled()) {
this.authSystem = new AuthSystem(this.config.getAuthInfo());
this.getPluginManager().registerCommand(null, new CommandChangePassword(this.authSystem));
}
if (this.reconnectHandler == null) {
this.reconnectHandler = new SQLReconnectHandler();
}

View file

@ -28,7 +28,7 @@ public class ServerConnection implements Server {
@Override
public synchronized void disconnect(final String reason) {
if (!this.ch.isClosed()) {
if (this.ch != null && !this.ch.isClosed()) {
this.unsafe().sendPacket(new PacketFFKick(reason));
this.ch.getHandle().eventLoop().schedule((Runnable) new Runnable() {
@Override
@ -41,7 +41,7 @@ public class ServerConnection implements Server {
@Override
public InetSocketAddress getAddress() {
return this.getInfo().getAddress();
return this.getInfo() == null ? null : this.getInfo().getAddress();
}
@Override
@ -54,7 +54,7 @@ public class ServerConnection implements Server {
this.unsafe = new Connection.Unsafe() {
@Override
public void sendPacket(final DefinedPacket packet) {
ServerConnection.this.ch.write(packet);
if (ServerConnection.this.ch != null) ServerConnection.this.ch.write(packet);
}
};
this.ch = ch;

View file

@ -1,35 +1,27 @@
package net.md_5.bungee.api.config;
import java.io.File;
public class AuthServiceInfo {
private final boolean enabled;
private final String limbo;
private final File authfile;
private final int timeout;
private final String authfile;
private final int ipLimit;
public AuthServiceInfo(boolean enabled, String limbo, File authfile, int timeout) {
public AuthServiceInfo(boolean enabled, String authfile, int timeout) {
this.enabled = enabled;
this.limbo = limbo;
this.authfile = authfile;
this.timeout = timeout;
this.ipLimit = timeout;
}
public boolean isEnabled() {
return enabled;
}
public String getLimbo() {
return limbo;
}
public File getAuthfile() {
public String getAuthfile() {
return authfile;
}
public int getTimeout() {
return timeout;
public int getIpLimit() {
return ipLimit;
}
}

View file

@ -0,0 +1,34 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.AuthSystem;
public class CommandChangePassword extends Command {
private final AuthSystem authSystem;
public CommandChangePassword(AuthSystem authSystem) {
super("changepassword", "bungeecord.command.eag.changepassword", new String[] { "changepwd", "changepasswd", "changepass" });
this.authSystem = authSystem;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
if (!(sender instanceof ProxiedPlayer)) {
return;
}
String username = sender.getName();
if (args.length == 0 || args.length == 1) {
sender.sendMessage("\u00A7cUsage: /changepassword <oldPassword> <newPassword>");
} else if (this.authSystem.login(username, args[0])) {
if (this.authSystem.changePass(username, args[1])) {
sender.sendMessage("\u00A7cPassword changed successfully!");
} else {
sender.sendMessage("\u00A7cUnable to change your password...");
}
} else {
sender.sendMessage("\u00A7cThe old password specified is incorrect!");
}
}
}

View file

@ -70,7 +70,7 @@ public class YamlConfig implements ConfigurationAdapter {
}
final Map<String, Object> permissions = this.get("permissions", new HashMap<String, Object>());
if (permissions.isEmpty()) {
permissions.put("default", Arrays.asList("bungeecord.command.server", "bungeecord.command.list", "bungeecord.command.eag.domain"));
permissions.put("default", Arrays.asList("bungeecord.command.server", "bungeecord.command.list", "bungeecord.command.eag.domain", "bungeecord.command.eag.changepassword"));
permissions.put("admin", Arrays.asList("bungeecord.command.alert", "bungeecord.command.end", "bungeecord.command.ip", "bungeecord.command.reload",
"bungeecord.command.eag.ban", "bungeecord.command.eag.banwildcard", "bungeecord.command.eag.banip", "bungeecord.command.eag.banregex",
"bungeecord.command.eag.reloadban", "bungeecord.command.eag.banned", "bungeecord.command.eag.banlist", "bungeecord.command.eag.unban", "bungeecord.command.eag.ratelimit",
@ -274,9 +274,8 @@ public class YamlConfig implements ConfigurationAdapter {
@Override
public AuthServiceInfo getAuthSettings() {
//final Map<String, Object> auth = this.get("authservice", new HashMap<String, Object>());
//return new AuthServiceInfo(this.get("enabled", true, auth), this.get("limbo", "lobby", auth), new File(this.get("authfile", "passwords.yml", auth)), this.get("timeout", 30, auth));
return null;
final Map<String, Object> auth = this.get("authservice", new HashMap<String, Object>());
return new AuthServiceInfo(this.get("enabled", false, auth), this.get("authfile", "auth.uwu", auth), this.get("ip_limit", 0, auth));
}
@Override

View file

@ -35,6 +35,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.eaglercraft.AuthHandler;
import net.md_5.bungee.eaglercraft.BanList;
import net.md_5.bungee.eaglercraft.BanList.BanCheck;
import net.md_5.bungee.eaglercraft.BanList.BanState;
@ -47,15 +48,7 @@ import net.md_5.bungee.netty.PacketDecoder;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.Forge;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.Packet1Login;
import net.md_5.bungee.protocol.packet.Packet2Handshake;
import net.md_5.bungee.protocol.packet.PacketCDClientStatus;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.protocol.packet.PacketFCEncryptionResponse;
import net.md_5.bungee.protocol.packet.PacketFDEncryptionRequest;
import net.md_5.bungee.protocol.packet.PacketFEPing;
import net.md_5.bungee.protocol.packet.PacketFFKick;
import net.md_5.bungee.protocol.packet.*;
public class InitialHandler extends PacketHandler implements PendingConnection {
private final ProxyServer bungee;
@ -259,10 +252,15 @@ public class InitialHandler extends PacketHandler implements PendingConnection {
userCon.getAttachment().put("origin", origin);
}
userCon.init();
this.bungee.getPluginManager().callEvent(new PostLoginEvent(userCon));
((HandlerBoss) this.ch.getHandle().pipeline().get((Class) HandlerBoss.class)).setHandler(new UpstreamBridge(this.bungee, userCon));
final ServerInfo server = this.bungee.getReconnectHandler().getServer(userCon);
userCon.connect(server, true);
HandlerBoss handlerBoss = ((HandlerBoss) this.ch.getHandle().pipeline().get((Class) HandlerBoss.class));
if (BungeeCord.getInstance().config.getAuthInfo().isEnabled()) {
handlerBoss.setHandler(new AuthHandler(this.bungee, userCon, handlerBoss));
} else {
this.bungee.getPluginManager().callEvent(new PostLoginEvent(userCon));
handlerBoss.setHandler(new UpstreamBridge(this.bungee, userCon));
final ServerInfo server = this.bungee.getReconnectHandler().getServer(userCon);
userCon.connect(server, true);
}
this.thisState = State.FINISHED;
throw new CancelSendSignal();
}

View file

@ -55,7 +55,7 @@ public class UpstreamBridge extends PacketHandler {
@Override
public void handle(final byte[] buf) throws Exception {
EntityMap.rewrite(buf, this.con.getClientEntityId(), this.con.getServerEntityId());
if (this.con.getServer() != null) {
if (this.con.getServer() != null && this.con.getServer().getCh() != null) {
this.con.getServer().getCh().write(buf);
}
}

View file

@ -0,0 +1,149 @@
package net.md_5.bungee.eaglercraft;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.connection.UpstreamBridge;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.protocol.packet.Packet1Login;
import net.md_5.bungee.protocol.packet.Packet9Respawn;
import net.md_5.bungee.protocol.packet.Packet0DPositionAndLook;
import net.md_5.bungee.protocol.packet.Packet3Chat;
import net.md_5.bungee.protocol.packet.Packet0KeepAlive;
import net.md_5.bungee.protocol.packet.PacketCCSettings;
public class AuthHandler extends PacketHandler {
private static final AuthSystem authSystem = BungeeCord.getInstance().authSystem;
private final ProxyServer bungee;
private final UserConnection con;
private final HandlerBoss handlerBoss;
private final String username;
private boolean loggedIn = false;
public AuthHandler(final ProxyServer bungee, final UserConnection con, final HandlerBoss handlerBoss) {
this.bungee = bungee;
this.con = con;
this.handlerBoss = handlerBoss;
this.username = this.con.getName();
new Thread(() -> {
for (int i = 0; i < 120; i++) {
if (this.loggedIn) break;
try {
Thread.sleep(250);
} catch (InterruptedException ignored) { }
}
if (this.loggedIn) return;
this.con.disconnect("You did not login in time!");
}).start();
this.con.unsafe().sendPacket(new Packet1Login(0, "END", (byte) 2, 1, (byte) 0, (byte) 0, (byte) 1));
this.con.unsafe().sendPacket(new Packet9Respawn(1, (byte) 0, (byte) 2, (short) 255, "END"));
this.con.unsafe().sendPacket(new Packet0DPositionAndLook(0, 0, 0, 0, 0f, 0f, true));
if (authSystem.isRegistered(this.username)) {
this.con.sendMessage("\u00A7cPlease login to continue! /login <password>");
} else {
this.con.sendMessage("\u00A7cPlease register to continue! /register <password> <confirmPassword>");
}
}
@Override
public void exception(final Throwable t) throws Exception {
this.con.disconnect(Util.exception(t));
}
@Override
public void disconnected(final ChannelWrapper channel) throws Exception {
this.loggedIn = true;
}
@Override
public void handle(final Packet0KeepAlive alive) throws Exception {
if (alive.getRandomId() == this.con.getSentPingId()) {
final int newPing = (int) (System.currentTimeMillis() - this.con.getSentPingTime());
this.con.setPing(newPing);
}
}
@Override
public void handle(final Packet3Chat chat) throws Exception {
String message = chat.getMessage();
if (message.startsWith("/")) {
String[] args = message.substring(1).trim().split(" ");
switch (args[0]) {
case "login":
case "l":
if (args.length == 1) {
this.con.sendMessage("\u00A7cYou must specify a password to login! /login <password>");
} else if (!authSystem.isRegistered(this.username)) {
this.con.sendMessage("\u00A7cThis username is not registered on this server!");
} else if (authSystem.login(this.username, args[1])) {
this.onLogin();
} else {
this.con.sendMessage("\u00A7cThat password is invalid!");
}
break;
case "register":
case "reg":
if (args.length == 1 || args.length == 2) {
this.con.sendMessage("\u00A7cUsage: /" + args[0].toLowerCase() + " <password> <confirmPassword>");
} else if (!args[1].equals(args[2])) {
this.con.sendMessage("\u00A7cThose passwords do not match!");
} else if (authSystem.isRegistered(this.username)) {
this.con.sendMessage("\u00A7cThis username is already registered!");
} else if (authSystem.register(this.username, args[1], this.con.getAddress().toString())) {
this.onLogin();
} else {
this.con.sendMessage("\u00A7cUnable to register...");
}
break;
case "changepassword":
case "changepasswd":
case "changepwd":
case "changepass":
if (args.length == 1 || args.length == 2) {
this.con.sendMessage("\u00A7cUsage: /" + args[0].toLowerCase() + " <oldPassword> <newPassword>");
} else if (authSystem.login(this.username, args[1])) {
if (authSystem.changePass(this.username, args[2])) {
this.con.sendMessage("\u00A7cPassword changed successfully!");
} else {
this.con.sendMessage("\u00A7cUnable to change your password...");
}
} else {
this.con.sendMessage("\u00A7cThe old password specified is incorrect!");
}
break;
default:
}
}
}
private void onLogin() {
this.loggedIn = true;
this.bungee.getPluginManager().callEvent(new PostLoginEvent(this.con));
handlerBoss.setHandler(new UpstreamBridge(this.bungee, this.con));
final ServerInfo server = this.bungee.getReconnectHandler().getServer(this.con);
this.con.setServer(new ServerConnection(null, null));
this.con.connect(server, true);
}
@Override
public void handle(final PacketCCSettings settings) throws Exception {
this.con.setSettings(settings);
}
@Override
public String toString() {
return "[" + this.con.getName() + "] -> AuthHandler";
}
}

View file

@ -0,0 +1,136 @@
package net.md_5.bungee.eaglercraft;
import com.google.common.hash.Hashing;
import net.md_5.bungee.api.config.AuthServiceInfo;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class AuthSystem {
private final String authFileName;
private final int ipLimit;
public AuthSystem(AuthServiceInfo authInfo) {
this.authFileName = authInfo.getAuthfile();
this.ipLimit = authInfo.getIpLimit();
this.readDatabase();
}
private static class AuthData {
public String passHash;
public Set<String> ips;
public AuthData(String p, Set<String> i) {
passHash = p;
ips = i;
}
}
private final Map<String, AuthData> database = new HashMap<>();
public boolean register(String username, String password, String ip) {
synchronized (database) {
AuthData authData = database.get(username);
if (authData != null) return false;
if (isIpAtTheLimit(ip)) return false;
String hash = Hashing.sha256().hashString(password).toString();
Set<String> initIps = new HashSet<>();
initIps.add(ip);
database.put(username, new AuthData(hash, initIps));
writeDatabase();
return true;
}
}
public boolean isRegistered(String username) {
synchronized (database) {
return database.containsKey(username);
}
}
public boolean changePass(String username, String password) {
synchronized (database) {
AuthData authData = database.get(username);
authData.passHash = Hashing.sha256().hashString(password).toString();
writeDatabase();
return true;
}
}
public boolean login(String username, String password) {
synchronized (database) {
AuthData authData = database.get(username);
if (authData == null) return false;
return authData.passHash.equals(Hashing.sha256().hashString(password).toString());
}
}
private boolean isIpAtTheLimit(String ip) {
synchronized (database) {
if (this.ipLimit <= 0) return false;
int num = 0;
for (AuthData authData : database.values()) {
if (authData.ips.contains(ip)) num++;
if (num >= this.ipLimit) {
return true;
}
}
return false;
}
}
// only use once, on load
public void readDatabase() {
synchronized (database) {
try {
File authFile = new File(this.authFileName);
if (!authFile.exists()) authFile.createNewFile();
Map<String, AuthData> cache = new HashMap<>();
String[] lines = new String(Files.readAllBytes(authFile.toPath())).trim().split("\n");
if (lines.length == 1 && lines[0].isEmpty()) return;
for (String line : lines) {
String[] pieces = line.split("\u0000");
cache.put(pieces[0], new AuthData(pieces[2], new HashSet<>(Arrays.asList(pieces[1].split("§")))));
}
database.clear();
database.putAll(cache);
cache.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void writeDatabase() {
synchronized (database) {
StringBuilder out = new StringBuilder();
for (String username : database.keySet()) {
AuthData entry = database.get(username);
out.append(username);
out.append("\u0000");
out.append(String.join("§", entry.ips));
out.append("\u0000");
out.append(entry.passHash);
out.append("\n");
}
try {
Files.write(Paths.get(this.authFileName), out.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View file

@ -1,52 +0,0 @@
package net.md_5.bungee.eaglercraft;
import java.util.Collections;
import java.util.HashSet;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginDescription;
import net.md_5.bungee.event.EventHandler;
public class PluginEaglerAuth extends Plugin implements Listener {
public PluginEaglerAuth() {
super(new PluginDescription("EaglerAuth", PluginEaglerAuth.class.getName(), "1.0.0", "LAX1DUDE", Collections.emptySet(), null));
}
@Override
public void onLoad() {
}
@Override
public void onEnable() {
getProxy().getPluginManager().registerListener(this, this);
}
@Override
public void onDisable() {
}
private final HashSet<ProxiedPlayer> playersInLimbo = new HashSet();
@EventHandler
public void onPostLogin(PostLoginEvent event) {
//playersInLimbo.add(event.getPlayer());
}
@EventHandler
public void onServerConnect(ServerConnectEvent event) {
ProxiedPlayer player = event.getPlayer();
//if(playersInLimbo.contains(player)) {
// event.setCancelled(true);
// player.unsafe().sendPacket(new Packet1Login(0, "END", (byte) 1, 1, (byte) 0, (byte) 0, (byte) 1));
// player.unsafe().sendPacket(new Packet9Respawn(1, (byte) 0, (byte) 1, (short) 255, "END"));
//}
}
}

View file

@ -55,4 +55,7 @@ public abstract class AbstractPacketHandler {
public void handle(final PacketFFKick kick) throws Exception {
}
public void handle(final Packet0DPositionAndLook positionAndLook) throws Exception {
}
}

View file

@ -0,0 +1,114 @@
package net.md_5.bungee.protocol.packet;
import io.netty.buffer.ByteBuf;
public class Packet0DPositionAndLook extends DefinedPacket {
private double x;
private double y;
private double stance;
private double z;
private float yaw;
private float pitch;
private boolean onGround;
private Packet0DPositionAndLook() {
super(13);
}
public Packet0DPositionAndLook(final int x, final int y, final double stance, final double z, final float yaw, final float pitch, final boolean onGround) {
this();
this.x = x;
this.y = y;
this.stance = stance;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.onGround = onGround;
}
@Override
public void read(final ByteBuf buf) {
this.x = buf.readDouble();
this.y = buf.readDouble();
this.stance = buf.readDouble();
this.z = buf.readDouble();
this.yaw = buf.readFloat();
this.pitch = buf.readFloat();
this.onGround = buf.readBoolean();
}
@Override
public void write(final ByteBuf buf) {
buf.writeDouble(this.x);
buf.writeDouble(this.y);
buf.writeDouble(this.stance);
buf.writeDouble(this.z);
buf.writeFloat(this.yaw);
buf.writeFloat(this.pitch);
buf.writeBoolean(this.onGround);
}
@Override
public void handle(final AbstractPacketHandler handler) throws Exception {
handler.handle(this);
}
@Override
public String toString() {
return "Packet0DPositionAndLook(tooLazyToFillThisInLOL)";
}
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Packet0DPositionAndLook)) {
return false;
}
final Packet0DPositionAndLook other = (Packet0DPositionAndLook) o;
if (!other.canEqual(this)) {
return false;
}
if (this.x != other.x) {
return false;
}
if (this.y != other.y) {
return false;
}
if (this.stance != other.stance) {
return false;
}
if (this.z != other.z) {
return false;
}
if (this.yaw != other.yaw) {
return false;
}
if (this.pitch != other.pitch) {
return false;
}
if (this.onGround != other.onGround) {
return false;
}
return false;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = result * 31 + Double.hashCode(this.x);
result = result * 31 + Double.hashCode(this.y);
result = result * 31 + Double.hashCode(this.stance);
result = result * 31 + Double.hashCode(this.z);
result = result * 31 + Float.hashCode(this.yaw);
result = result * 31 + Float.hashCode(this.pitch);
result = result * 31 + Boolean.hashCode(this.onGround);
return result;
}
public boolean canEqual(final Object other) {
return other instanceof Packet0DPositionAndLook;
}
}