diff --git a/akashi.pro b/akashi.pro
index e18ab4b..42a09d1 100644
--- a/akashi.pro
+++ b/akashi.pro
@@ -1,4 +1,4 @@
-QT       += network websockets core
+QT       += network websockets core sql
 QT       -= gui
 TEMPLATE = app
 
@@ -21,6 +21,26 @@ MOC_DIR = $$PWD/build
 
 RC_ICONS = resource/icon/akashi.ico
 
-SOURCES += $$files($$PWD/src/*.cpp)
+SOURCES += src/advertiser.cpp \
+    src/aoclient.cpp \
+    src/aopacket.cpp \
+    src/area_data.cpp \
+    src/ban_manager.cpp \
+    src/config_manager.cpp \
+    src/icchatpacket.cpp \
+    src/main.cpp \
+    src/server.cpp \
+    src/ws_client.cpp \
+    src/ws_proxy.cpp
 
-HEADERS += $$files($$PWD/include/*.h)
+
+HEADERS += include/advertiser.h \
+    include/aoclient.h \
+    include/aopacket.h \
+    include/area_data.h \
+    include/ban_manager.h \
+    include/config_manager.h \
+    include/icchatpacket.h \
+    include/server.h \
+    include/ws_client.h \
+    include/ws_proxy.h
diff --git a/bin/config_sample/config.ini b/bin/config_sample/config.ini
index fbeac9f..06a05a8 100644
--- a/bin/config_sample/config.ini
+++ b/bin/config_sample/config.ini
@@ -16,4 +16,7 @@ server_description=This is a placeholder server description. Tell the world of A
 server_name=An Unnamed Server
 webao_enable=true
 webao_port=27017
-modpass=changeme
\ No newline at end of file
+
+[Moderators]
+; Make sure you change this before running your server!
+password_change_me=user_change_me
\ No newline at end of file
diff --git a/include/ban_manager.h b/include/ban_manager.h
new file mode 100644
index 0000000..2a50085
--- /dev/null
+++ b/include/ban_manager.h
@@ -0,0 +1,45 @@
+//////////////////////////////////////////////////////////////////////////////////////
+//    akashi - a server for Attorney Online 2                                       //
+//    Copyright (C) 2020  scatterflower                                           //
+//                                                                                  //
+//    This program is free software: you can redistribute it and/or modify          //
+//    it under the terms of the GNU Affero General Public License as                //
+//    published by the Free Software Foundation, either version 3 of the            //
+//    License, or (at your option) any later version.                               //
+//                                                                                  //
+//    This program is distributed in the hope that it will be useful,               //
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of                //
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 //
+//    GNU Affero General Public License for more details.                           //
+//                                                                                  //
+//    You should have received a copy of the GNU Affero General Public License      //
+//    along with this program.  If not, see <https://www.gnu.org/licenses/>.        //
+//////////////////////////////////////////////////////////////////////////////////////
+#ifndef BAN_MANAGER_H
+#define BAN_MANAGER_H
+
+#include <QDebug>
+#include <QHostAddress>
+#include <QString>
+#include <QSqlDatabase>
+#include <QSqlDriver>
+#include <QSqlError>
+#include <QSqlQuery>
+
+class BanManager{
+public:
+    BanManager();
+    ~BanManager();
+
+    bool isIPBanned(QHostAddress ip);
+    bool isHDIDBanned(QString hdid);
+
+    QString getBanReason(QHostAddress ip);
+    QString getBanReason(QString hdid);
+
+private:
+    const QString DRIVER;
+    QSqlDatabase db;
+};
+
+#endif // BAN_MANAGER_H
diff --git a/include/server.h b/include/server.h
index 3245883..ecada84 100644
--- a/include/server.h
+++ b/include/server.h
@@ -22,6 +22,7 @@
 #include "include/aopacket.h"
 #include "include/area_data.h"
 #include "include/ws_proxy.h"
+#include "include/ban_manager.h"
 
 #include <QCoreApplication>
 #include <QDebug>
@@ -52,6 +53,7 @@ class Server : public QObject {
     QVector<AreaData*> areas;
     QStringList area_names;
     QStringList music_list;
+    BanManager* ban_manager;
 
   signals:
 
diff --git a/include/ws_client.h b/include/ws_client.h
index 8f2ca17..7c824bc 100644
--- a/include/ws_client.h
+++ b/include/ws_client.h
@@ -33,6 +33,7 @@ public slots:
     void onWsData(QString message);
     void onWsDisconnect();
     void onTcpDisconnect();
+    void onTcpConnect();
 
 
 private:
diff --git a/src/aoclient.cpp b/src/aoclient.cpp
index 41923fa..53bbddf 100644
--- a/src/aoclient.cpp
+++ b/src/aoclient.cpp
@@ -35,7 +35,7 @@ AOClient::AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent)
 void AOClient::clientData()
 {
     QString data = QString::fromUtf8(socket->readAll());
-    qDebug() << "From" << remote_ip << ":" << data;
+    // qDebug() << "From" << remote_ip << ":" << data;
 
     if (is_partial) {
         data = partial_packet + data;
@@ -55,7 +55,7 @@ void AOClient::clientData()
 
 void AOClient::clientDisconnected()
 {
-    qDebug() << remote_ip << "disconnected";
+    qDebug() << remote_ip.toString() << "disconnected";
     if (joined) {
         server->player_count--;
         server->areas[current_area]->player_count--;
@@ -76,6 +76,11 @@ void AOClient::handlePacket(AOPacket packet)
     // Lord forgive me
     if (packet.header == "HI") {
         setHwid(packet.contents[0]);
+        if(server->ban_manager->isHDIDBanned(getHwid())) {
+            sendPacket("BD", {server->ban_manager->getBanReason(getHwid())});
+            socket->close();
+            return;
+        }
         sendPacket("ID", {"271828", "akashi", QCoreApplication::applicationVersion()});
     }
     else if (packet.header == "ID") {
@@ -222,6 +227,19 @@ void AOClient::handlePacket(AOPacket packet)
         server->broadcast(AOPacket("HP", {"1", QString::number(area->def_hp)}), area->index);
         server->broadcast(AOPacket("HP", {"2", QString::number(area->pro_hp)}), area->index);
     }
+    else if (packet.header == "WSIP") {
+        // Special packet to set remote IP from the webao proxy
+        // Only valid if from a local ip
+        if (remote_ip.isLoopback()) {
+            if(server->ban_manager->isIPBanned(QHostAddress(packet.contents[0]))) {
+                sendPacket("BD", {server->ban_manager->getBanReason(QHostAddress(packet.contents[0]))});
+                socket->close();
+                return;
+            }
+            qDebug() << "ws ip set to" << packet.contents[0];
+            remote_ip = QHostAddress(packet.contents[0]);
+        }
+    }
     else {
         qDebug() << "Unimplemented packet:" << packet.header;
         qDebug() << packet.contents;
@@ -368,7 +386,7 @@ void AOClient::fullArup() {
 
 void AOClient::sendPacket(AOPacket packet)
 {
-    qDebug() << "Sent packet:" << packet.header << ":" << packet.contents;
+    // qDebug() << "Sent packet:" << packet.header << ":" << packet.contents;
     socket->write(packet.toUtf8());
     socket->flush();
 }
@@ -401,6 +419,7 @@ void AOClient::setHwid(QString p_hwid)
     hash.addData(concat_ip_id.toUtf8());
 
     ipid = hash.result().toHex().right(8); // Use the last 8 characters (4 bytes)
+    qDebug() << "IP:" << remote_ip.toString() << "HDID:" << p_hwid << "IPID:" << ipid;
 }
 
 void AOClient::sendServerMessage(QString message)
diff --git a/src/ban_manager.cpp b/src/ban_manager.cpp
new file mode 100644
index 0000000..5b72f7e
--- /dev/null
+++ b/src/ban_manager.cpp
@@ -0,0 +1,75 @@
+//////////////////////////////////////////////////////////////////////////////////////
+//    akashi - a server for Attorney Online 2                                       //
+//    Copyright (C) 2020  scatterflower                                           //
+//                                                                                  //
+//    This program is free software: you can redistribute it and/or modify          //
+//    it under the terms of the GNU Affero General Public License as                //
+//    published by the Free Software Foundation, either version 3 of the            //
+//    License, or (at your option) any later version.                               //
+//                                                                                  //
+//    This program is distributed in the hope that it will be useful,               //
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of                //
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 //
+//    GNU Affero General Public License for more details.                           //
+//                                                                                  //
+//    You should have received a copy of the GNU Affero General Public License      //
+//    along with this program.  If not, see <https://www.gnu.org/licenses/>.        //
+//////////////////////////////////////////////////////////////////////////////////////
+#include "include/ban_manager.h"
+
+BanManager::BanManager() :
+    DRIVER("QSQLITE")
+{
+    db = QSqlDatabase::addDatabase(DRIVER);
+    db.setDatabaseName("config/bans.db");
+    if (!db.open())
+        qCritical() << "Database Error:" << db.lastError();
+    QSqlQuery create_table_query("CREATE TABLE IF NOT EXISTS bans ('ID' INTEGER, 'IPID' TEXT, 'HDID' TEXT, 'IP' TEXT, 'TIME' INTEGER, 'REASON' TEXT, PRIMARY KEY('ID' AUTOINCREMENT));");
+}
+
+bool BanManager::isIPBanned(QHostAddress ip)
+{
+    QSqlQuery query;
+    query.prepare("SELECT ID FROM BANS WHERE IP = ?");
+    query.addBindValue(ip.toString());
+    return query.first();
+}
+
+bool BanManager::isHDIDBanned(QString hdid)
+{
+    QSqlQuery query;
+    query.prepare("SELECT ID FROM BANS WHERE HDID = ?");
+    query.addBindValue(hdid);
+    return query.first();
+}
+
+QString BanManager::getBanReason(QHostAddress ip)
+{
+    QSqlQuery query;
+    query.prepare("SELECT ID FROM BANS WHERE IP = ?");
+    query.addBindValue(ip.toString());
+    if (query.first()) {
+        return query.value(0).toString();
+    }
+    else {
+        return "Ban reason not found.";
+    }
+}
+
+QString BanManager::getBanReason(QString hdid)
+{
+    QSqlQuery query;
+    query.prepare("SELECT ID FROM BANS WHERE HDID = ?");
+    query.addBindValue(hdid);
+    if (query.first()) {
+        return query.value(0).toString();
+    }
+    else {
+        return "Ban reason not found.";
+    }
+}
+
+BanManager::~BanManager()
+{
+    db.close();
+}
diff --git a/src/server.cpp b/src/server.cpp
index 4ef6fbb..842242a 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -26,6 +26,8 @@ Server::Server(int p_port, int p_ws_port, QObject* parent) : QObject(parent)
     ws_port = p_ws_port;
 
     player_count = 0;
+
+    ban_manager = new BanManager();
 }
 
 void Server::start()
@@ -77,11 +79,18 @@ void Server::clientConnected()
 {
     QTcpSocket* socket = server->nextPendingConnection();
     AOClient* client = new AOClient(this, socket, this);
+    if (ban_manager->isIPBanned(socket->peerAddress())) {
+        AOPacket ban_reason("BD", {ban_manager->getBanReason(socket->peerAddress())});
+        socket->write(ban_reason.toUtf8());
+        socket->flush();
+        client->deleteLater();
+        socket->close();
+        return;
+    }
     clients.append(client);
     connect(socket, &QTcpSocket::disconnected, client,
             &AOClient::clientDisconnected);
     connect(socket, &QTcpSocket::disconnected, this, [=] {
-        qDebug() << "removed client" << client->getIpid();
         clients.removeAll(client);
         client->deleteLater();
     });
diff --git a/src/ws_client.cpp b/src/ws_client.cpp
index 4ce14c3..7865fb0 100644
--- a/src/ws_client.cpp
+++ b/src/ws_client.cpp
@@ -52,6 +52,12 @@ void WSClient::onTcpDisconnect()
     web_socket->close();
 }
 
+void WSClient::onTcpConnect()
+{
+    tcp_socket->write(QString("WSIP#" + web_socket->peerAddress().toString() + "#%").toUtf8());
+    tcp_socket->flush();
+}
+
 WSClient::~WSClient()
 {
     tcp_socket->deleteLater();
diff --git a/src/ws_proxy.cpp b/src/ws_proxy.cpp
index 766f787..619b434 100644
--- a/src/ws_proxy.cpp
+++ b/src/ws_proxy.cpp
@@ -51,6 +51,7 @@ void WSProxy::wsConnected()
         clients.removeAll(client);
         client->deleteLater();
     });
+    connect(new_tcp, &QTcpSocket::connected, client, &WSClient::onTcpConnect);
 
     new_tcp->connectToHost(QHostAddress::LocalHost, local_port);
 }