From b102a05a3655a6bc03b6fef1971d04f983b8b956 Mon Sep 17 00:00:00 2001 From: woogerboy21 Date: Wed, 12 Aug 2015 21:55:40 -0400 Subject: [PATCH] Add ability to ban by client id --- cockatrice/src/localserver.cpp | 2 +- cockatrice/src/localserver.h | 2 +- cockatrice/src/remoteclient.cpp | 2 + cockatrice/src/user_context_menu.cpp | 1 + cockatrice/src/userlist.cpp | 17 +++++- cockatrice/src/userlist.h | 5 +- common/pb/moderator_commands.proto | 1 + common/pb/serverinfo_user.proto | 1 + common/pb/session_commands.proto | 1 + common/server.cpp | 2 +- common/server_database_interface.h | 4 +- common/serverinfo_user_container.cpp | 1 + .../migrations/servatrice_0004_to_0005.sql | 5 ++ servatrice/servatrice.sql | 3 +- .../src/servatrice_database_interface.cpp | 53 ++++++++++++++++--- .../src/servatrice_database_interface.h | 9 ++-- servatrice/src/serversocketinterface.cpp | 29 ++++++++-- 17 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 servatrice/migrations/servatrice_0004_to_0005.sql diff --git a/cockatrice/src/localserver.cpp b/cockatrice/src/localserver.cpp index a7eda3d1..613964fd 100644 --- a/cockatrice/src/localserver.cpp +++ b/cockatrice/src/localserver.cpp @@ -33,7 +33,7 @@ ServerInfo_User LocalServer_DatabaseInterface::getUserData(const QString &name, return result; } -AuthenticationResult LocalServer_DatabaseInterface::checkUserPassword(Server_ProtocolHandler * /* handler */, const QString & /* user */, const QString & /* password */, QString & /* reasonStr */, int & /* secondsLeft */) +AuthenticationResult LocalServer_DatabaseInterface::checkUserPassword(Server_ProtocolHandler * /* handler */, const QString & /* user */, const QString & /* password */, const QString & /* clientId */, QString & /* reasonStr */, int & /* secondsLeft */) { return UnknownUser; } diff --git a/cockatrice/src/localserver.h b/cockatrice/src/localserver.h index 276ebf90..221318c7 100644 --- a/cockatrice/src/localserver.h +++ b/cockatrice/src/localserver.h @@ -24,7 +24,7 @@ protected: ServerInfo_User getUserData(const QString &name, bool withId = false); public: LocalServer_DatabaseInterface(LocalServer *_localServer); - AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); + AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, const QString &clientId, QString &reasonStr, int &secondsLeft); int getNextGameId() { return localServer->getNextLocalGameId(); } int getNextReplayId() { return -1; } int getActiveUserCount() { return 0; } diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index 68122d2a..96ee1eea 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -11,6 +11,7 @@ #include "pb/server_message.pb.h" #include "pb/event_server_identification.pb.h" #include "settingscache.h" +#include "main.h" static const unsigned int protocolVersion = 14; @@ -78,6 +79,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica cmdRegister.set_gender((ServerInfo_User_Gender) gender); cmdRegister.set_country(country.toStdString()); cmdRegister.set_real_name(realName.toStdString()); + cmdRegister.set_clientid(settingsCache->getClientID().toStdString()); PendingCommand *pend = prepareSessionCommand(cmdRegister); connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(registerResponse(Response))); diff --git a/cockatrice/src/user_context_menu.cpp b/cockatrice/src/user_context_menu.cpp index ace5a44b..758f99e1 100644 --- a/cockatrice/src/user_context_menu.cpp +++ b/cockatrice/src/user_context_menu.cpp @@ -99,6 +99,7 @@ void UserContextMenu::banUser_dialogFinished() cmd.set_minutes(dlg->getMinutes()); cmd.set_reason(dlg->getReason().toStdString()); cmd.set_visible_reason(dlg->getVisibleReason().toStdString()); + cmd.set_clientid(dlg->getBanId().toStdString()); client->sendCommand(client->prepareModeratorCommand(cmd)); } diff --git a/cockatrice/src/userlist.cpp b/cockatrice/src/userlist.cpp index 6afbc610..59717608 100644 --- a/cockatrice/src/userlist.cpp +++ b/cockatrice/src/userlist.cpp @@ -36,11 +36,19 @@ BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) ipBanCheckBox = new QCheckBox(tr("ban &IP address")); ipBanCheckBox->setChecked(true); ipBanEdit = new QLineEdit(QString::fromStdString(info.address())); + idBanCheckBox = new QCheckBox(tr("ban client I&D")); + idBanCheckBox->setChecked(true); + idBanEdit = new QLineEdit(QString::fromStdString(info.clientid())); + if (QString::fromStdString(info.clientid()).isEmpty()) + idBanCheckBox->setChecked(false); + QGridLayout *banTypeGrid = new QGridLayout; banTypeGrid->addWidget(nameBanCheckBox, 0, 0); banTypeGrid->addWidget(nameBanEdit, 0, 1); banTypeGrid->addWidget(ipBanCheckBox, 1, 0); banTypeGrid->addWidget(ipBanEdit, 1, 1); + banTypeGrid->addWidget(idBanCheckBox, 2, 0); + banTypeGrid->addWidget(idBanEdit, 2, 1); QGroupBox *banTypeGroupBox = new QGroupBox(tr("Ban type")); banTypeGroupBox->setLayout(banTypeGrid); @@ -110,8 +118,8 @@ BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) void BanDialog::okClicked() { - if (!nameBanCheckBox->isChecked() && !ipBanCheckBox->isChecked()) { - QMessageBox::critical(this, tr("Error"), tr("You have to select a name-based or IP-based ban, or both.")); + if (!nameBanCheckBox->isChecked() && !ipBanCheckBox->isChecked() && !idBanCheckBox->isChecked()) { + QMessageBox::critical(this, tr("Error"), tr("You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban.")); return; } accept(); @@ -127,6 +135,11 @@ void BanDialog::enableTemporaryEdits(bool enabled) minutesEdit->setEnabled(enabled); } +QString BanDialog::getBanId() const +{ + return idBanCheckBox->isChecked() ? idBanEdit->text() : QString(); +} + QString BanDialog::getBanName() const { return nameBanCheckBox->isChecked() ? nameBanEdit->text() : QString(); diff --git a/cockatrice/src/userlist.h b/cockatrice/src/userlist.h index fc043b1d..3ac47a08 100644 --- a/cockatrice/src/userlist.h +++ b/cockatrice/src/userlist.h @@ -24,8 +24,8 @@ class BanDialog : public QDialog { Q_OBJECT private: QLabel *daysLabel, *hoursLabel, *minutesLabel; - QCheckBox *nameBanCheckBox, *ipBanCheckBox; - QLineEdit *nameBanEdit, *ipBanEdit; + QCheckBox *nameBanCheckBox, *ipBanCheckBox, *idBanCheckBox; + QLineEdit *nameBanEdit, *ipBanEdit, *idBanEdit; QSpinBox *daysEdit, *hoursEdit, *minutesEdit; QRadioButton *permanentRadio, *temporaryRadio; QPlainTextEdit *reasonEdit, *visibleReasonEdit; @@ -36,6 +36,7 @@ public: BanDialog(const ServerInfo_User &info, QWidget *parent = 0); QString getBanName() const; QString getBanIP() const; + QString getBanId() const; int getMinutes() const; QString getReason() const; QString getVisibleReason() const; diff --git a/common/pb/moderator_commands.proto b/common/pb/moderator_commands.proto index 18324ef1..5fe8a847 100644 --- a/common/pb/moderator_commands.proto +++ b/common/pb/moderator_commands.proto @@ -14,4 +14,5 @@ message Command_BanFromServer { optional uint32 minutes = 3; optional string reason = 4; optional string visible_reason = 5; + optional string clientid = 6; } diff --git a/common/pb/serverinfo_user.proto b/common/pb/serverinfo_user.proto index e93d09fe..71e5849e 100644 --- a/common/pb/serverinfo_user.proto +++ b/common/pb/serverinfo_user.proto @@ -23,4 +23,5 @@ message ServerInfo_User { optional uint64 session_id = 10; optional uint64 accountage_secs = 11; optional string email = 12; + optional string clientid = 13; } diff --git a/common/pb/session_commands.proto b/common/pb/session_commands.proto index fda1d8bc..d5564f3e 100644 --- a/common/pb/session_commands.proto +++ b/common/pb/session_commands.proto @@ -119,6 +119,7 @@ message Command_Register { // Country code of the user. 2 letter ISO format optional string country = 5; optional string real_name = 6; + optional string clientid = 7; } // User wants to activate an account diff --git a/common/server.cpp b/common/server.cpp index 543a2d1c..f8ea1ee4 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -112,7 +112,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString QWriteLocker locker(&clientsLock); - AuthenticationResult authState = databaseInterface->checkUserPassword(session, name, password, reasonStr, secondsLeft); + AuthenticationResult authState = databaseInterface->checkUserPassword(session, name, password, clientid, reasonStr, secondsLeft); if (authState == NotLoggedIn || authState == UserIsBanned || authState == UsernameInvalid || authState == UserIsInactive) return authState; diff --git a/common/server_database_interface.h b/common/server_database_interface.h index ebd944bc..58a50293 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -12,8 +12,8 @@ public: Server_DatabaseInterface(QObject *parent = 0) : QObject(parent) { } - virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft) = 0; - virtual bool checkUserIsBanned(const QString & /* ipAddress */, const QString & /* userName */, QString & /* banReason */, int & /* banSecondsRemaining */) { return false; } + virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, const QString &clientId, QString &reasonStr, int &secondsLeft) = 0; + virtual bool checkUserIsBanned(const QString & /* ipAddress */, const QString & /* userName */, const QString & /* clientId */, QString & /* banReason */, int & /* banSecondsRemaining */) { return false; } virtual bool activeUserExists(const QString & /* user */) { return false; } virtual bool userExists(const QString & /* user */) { return false; } virtual QMap getBuddyList(const QString & /* name */) { return QMap(); } diff --git a/common/serverinfo_user_container.cpp b/common/serverinfo_user_container.cpp index bf53a3f0..a0fd05c9 100644 --- a/common/serverinfo_user_container.cpp +++ b/common/serverinfo_user_container.cpp @@ -36,6 +36,7 @@ ServerInfo_User &ServerInfo_User_Container::copyUserInfo(ServerInfo_User &result if (!sessionInfo) { result.clear_session_id(); result.clear_address(); + result.clear_clientid(); } if (!internalInfo) { diff --git a/servatrice/migrations/servatrice_0004_to_0005.sql b/servatrice/migrations/servatrice_0004_to_0005.sql new file mode 100644 index 00000000..2d4ecf00 --- /dev/null +++ b/servatrice/migrations/servatrice_0004_to_0005.sql @@ -0,0 +1,5 @@ +-- Servatrice db migration from version 4 to version 5 + +alter table cockatrice_bans add clientid varchar(15) not null; + +UPDATE cockatrice_schema_version SET version=5 WHERE version=4; diff --git a/servatrice/servatrice.sql b/servatrice/servatrice.sql index 2a552ee4..4ffe8753 100644 --- a/servatrice/servatrice.sql +++ b/servatrice/servatrice.sql @@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` ( PRIMARY KEY (`version`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -INSERT INTO cockatrice_schema_version VALUES(4); +INSERT INTO cockatrice_schema_version VALUES(5); CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` ( `id` int(7) unsigned zerofill NOT NULL auto_increment, @@ -132,6 +132,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_bans` ( `minutes` int(6) NOT NULL, `reason` text NOT NULL, `visible_reason` text NOT NULL, + `clientid` varchar(15) NOT NULL, PRIMARY KEY (`user_name`,`time_from`), KEY `time_from` (`time_from`,`ip_address`), KEY `ip_address` (`ip_address`) diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index b1d75e0d..15dee6a2 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -229,7 +229,7 @@ QChar Servatrice_DatabaseInterface::getGenderChar(ServerInfo_User_Gender const & } } -AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &banSecondsLeft) +AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, const QString &clientId, QString &reasonStr, int &banSecondsLeft) { switch (server->getAuthenticationMethod()) { case Servatrice::AuthenticationNone: return UnknownUser; @@ -247,7 +247,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot if (!usernameIsValid(user, reasonStr)) return UsernameInvalid; - if (checkUserIsBanned(handler->getAddress(), user, reasonStr, banSecondsLeft)) + if (checkUserIsBanned(handler->getAddress(), user, clientId, reasonStr, banSecondsLeft)) return UserIsBanned; QSqlQuery *passwordQuery = prepareQuery("select password_sha512, active from {prefix}_users where name = :name"); @@ -280,7 +280,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot return UnknownUser; } -bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining) +bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, const QString &userName, const QString &clientId, QString &banReason, int &banSecondsRemaining) { if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) return false; @@ -291,11 +291,48 @@ bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, c } return - checkUserIsIpBanned(ipAddress, banReason, banSecondsRemaining) - || checkUserIsNameBanned(userName, banReason, banSecondsRemaining); + checkUserIsIpBanned(ipAddress, banReason, banSecondsRemaining) || checkUserIsNameBanned(userName, banReason, banSecondsRemaining) || checkUserIsIdBanned(clientId, banReason, banSecondsRemaining); } +bool Servatrice_DatabaseInterface::checkUserIsIdBanned(const QString &clientId, QString &banReason, int &banSecondsRemaining) +{ + if (clientId.isEmpty()) + return false; + + QSqlQuery *idBanQuery = prepareQuery( + "select" + " timestampdiff(second, now(), date_add(b.time_from, interval b.minutes minute))," + " b.minutes <=> 0," + " b.visible_reason" + " from {prefix}_bans b" + " where" + " b.time_from = (select max(c.time_from)" + " from {prefix}_bans c" + " where c.clientid = :id)" + " and b.clientid = :id2"); + + idBanQuery->bindValue(":id", clientId); + idBanQuery->bindValue(":id2", clientId); + if (!execSqlQuery(idBanQuery)) { + qDebug() << "Id ban check failed: SQL error." << idBanQuery->lastError(); + return false; + } + + if (idBanQuery->next()) { + const int secondsLeft = idBanQuery->value(0).toInt(); + const bool permanentBan = idBanQuery->value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { + banReason = idBanQuery->value(2).toString(); + banSecondsRemaining = permanentBan ? 0 : secondsLeft; + qDebug() << "User is banned by client id" << clientId; + return true; + } + } + return false; +} + + bool Servatrice_DatabaseInterface::checkUserIsNameBanned(const QString &userName, QString &banReason, int &banSecondsRemaining) { QSqlQuery *nameBanQuery = prepareQuery("select timestampdiff(second, now(), date_add(b.time_from, interval b.minutes minute)), b.minutes <=> 0, b.visible_reason from {prefix}_bans b where b.time_from = (select max(c.time_from) from {prefix}_bans c where c.user_name = :name2) and b.user_name = :name1"); @@ -477,6 +514,10 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer const QString email = query->value(8).toString(); if (!email.isEmpty()) result.set_email(email.toStdString()); + + const QString clientid = query->value(9).toString(); + if (!clientid.isEmpty()) + result.set_clientid(clientid.toStdString()); } return result; } @@ -491,7 +532,7 @@ ServerInfo_User Servatrice_DatabaseInterface::getUserData(const QString &name, b if (!checkSql()) return result; - QSqlQuery *query = prepareQuery("select id, name, admin, country, gender, realname, avatar_bmp, registrationDate, email from {prefix}_users where name = :name and active = 1"); + QSqlQuery *query = prepareQuery("select id, name, admin, country, gender, realname, avatar_bmp, registrationDate, email, clientid from {prefix}_users where name = :name and active = 1"); query->bindValue(":name", name); if (!execSqlQuery(query)) return result; diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 89e0dd3e..7e05fe8e 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -9,7 +9,7 @@ #include "server.h" #include "server_database_interface.h" -#define DATABASE_SCHEMA_VERSION 4 +#define DATABASE_SCHEMA_VERSION 5 class Servatrice; @@ -22,13 +22,14 @@ private: Servatrice *server; ServerInfo_User evalUserQueryResult(const QSqlQuery *query, bool complete, bool withId = false); /** Must be called after checkSql and server is known to be in auth mode. */ + bool checkUserIsIdBanned(const QString &clientId, QString &banReason, int &banSecondsRemaining); + /** Must be called after checkSql and server is known to be in auth mode. */ bool checkUserIsIpBanned(const QString &ipAddress, QString &banReason, int &banSecondsRemaining); /** Must be called after checkSql and server is known to be in auth mode. */ bool checkUserIsNameBanned(QString const &userName, QString &banReason, int &banSecondsRemaining); protected: - AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, - const QString &password, QString &reasonStr, int &secondsLeft); + AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, const QString &clientId, QString &reasonStr, int &secondsLeft); public slots: void initDatabase(const QSqlDatabase &_sqlDatabase); @@ -66,7 +67,7 @@ public: void unlockSessionTables(); bool userSessionExists(const QString &userName); bool usernameIsValid(const QString &user, QString & error); - bool checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining); + bool checkUserIsBanned(const QString &ipAddress, const QString &userName, const QString &clientId, QString &banReason, int &banSecondsRemaining); bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, QString &token, bool active = false); diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index abb2997a..db87c70d 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -756,20 +756,40 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban if (trustedSources.contains(address,Qt::CaseInsensitive)) address = ""; - QSqlQuery *query = sqlInterface->prepareQuery("insert into {prefix}_bans (user_name, ip_address, id_admin, time_from, minutes, reason, visible_reason) values(:user_name, :ip_address, :id_admin, NOW(), :minutes, :reason, :visible_reason)"); + QSqlQuery *query = sqlInterface->prepareQuery("insert into {prefix}_bans (user_name, ip_address, id_admin, time_from, minutes, reason, visible_reason, clientid) values(:user_name, :ip_address, :id_admin, NOW(), :minutes, :reason, :visible_reason, :client_id)"); query->bindValue(":user_name", userName); query->bindValue(":ip_address", address); query->bindValue(":id_admin", userInfo->id()); query->bindValue(":minutes", minutes); query->bindValue(":reason", QString::fromStdString(cmd.reason())); query->bindValue(":visible_reason", QString::fromStdString(cmd.visible_reason())); + query->bindValue(":client_id", QString::fromStdString(cmd.clientid())); sqlInterface->execSqlQuery(query); servatrice->clientsLock.lockForRead(); QList userList = servatrice->getUsersWithAddressAsList(QHostAddress(address)); - ServerSocketInterface *user = static_cast(server->getUsers().value(userName)); - if (user && !userList.contains(user)) + + if (!userName.isEmpty()) { + ServerSocketInterface *user = static_cast(server->getUsers().value(userName)); userList.append(user); + } + + if (userName.isEmpty() && address.isEmpty()) { + QSqlQuery *query = sqlInterface->prepareQuery("select name from {prefix}_users where clientid = :client_id"); + query->bindValue(":client_id", QString::fromStdString(cmd.clientid())); + sqlInterface->execSqlQuery(query); + if (!sqlInterface->execSqlQuery(query)){ + qDebug("ClientID username ban lookup failed: SQL Error"); + } else { + while (query->next()) { + userName = query->value(0).toString(); + ServerSocketInterface *user = static_cast(server->getUsers().value(userName)); + if (user && !userList.contains(user)) + userList.append(user); + } + } + } + if (!userList.isEmpty()) { Event_ConnectionClosed event; event.set_reason(Event_ConnectionClosed::BANNED); @@ -792,6 +812,7 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban Response::ResponseCode ServerSocketInterface::cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc) { QString userName = QString::fromStdString(cmd.user_name()); + QString clientId = QString::fromStdString(cmd.clientid()); qDebug() << "Got register command: " << userName; bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); @@ -822,7 +843,7 @@ Response::ResponseCode ServerSocketInterface::cmdRegisterAccount(const Command_R QString banReason; int banSecondsRemaining; - if (sqlInterface->checkUserIsBanned(this->getAddress(), userName, banReason, banSecondsRemaining)) + if (sqlInterface->checkUserIsBanned(this->getAddress(), userName, clientId, banReason, banSecondsRemaining)) { Response_Register *re = new Response_Register; re->set_denied_reason_str(banReason.toStdString());