Merge branch 'master' into tooomm-qt5

This commit is contained in:
tooomm 2026-08-27 06:33:34 +02:00
commit 3bc08ef94c
357 changed files with 23069 additions and 2916 deletions

View file

@ -514,6 +514,23 @@ QList<ServerProperties> Servatrice::getServerList() const
return result;
}
std::shared_ptr<const Response_ReportStats> Servatrice::getCachedReportStats() const
{
QMutexLocker locker(&reportStatsMutex);
if (!reportStatsTimestamp.isValid() ||
reportStatsTimestamp.secsTo(QDateTime::currentDateTime()) >= reportStatsCacheTtlSeconds) {
return nullptr;
}
return reportStatsCache;
}
void Servatrice::cacheReportStats(const Response_ReportStats &stats)
{
QMutexLocker locker(&reportStatsMutex);
reportStatsCache = std::make_shared<const Response_ReportStats>(stats);
reportStatsTimestamp = QDateTime::currentDateTime();
}
int Servatrice::getUsersWithAddress(const QHostAddress &address) const
{
int result = 0;
@ -673,8 +690,27 @@ void Servatrice::statusUpdate()
}
}
SessionEvent *Servatrice::makeShutdownEvent() const
{
Event_ServerShutdown event;
event.set_reason(shutdownReason.toStdString());
event.set_minutes(static_cast<google::protobuf::uint32>(shutdownMinutes));
return Server_ProtocolHandler::prepareSessionEvent(event);
}
SessionEvent *Servatrice::getLoginSessionEvent() const
{
// Notify newly logged-in users of a pending server shutdown
QMutexLocker locker(&shutdownStateMutex);
if (shutdownTimer && shutdownMinutes > 0) {
return makeShutdownEvent();
}
return nullptr;
}
void Servatrice::scheduleShutdown(const QString &reason, int minutes)
{
shutdownStateMutex.lock();
shutdownReason = reason;
shutdownMinutes = minutes;
nextShutdownMessageMinutes = shutdownMinutes;
@ -683,6 +719,7 @@ void Servatrice::scheduleShutdown(const QString &reason, int minutes)
connect(shutdownTimer, SIGNAL(timeout()), this, SLOT(shutdownTimeout()));
shutdownTimer->start(60000);
}
shutdownStateMutex.unlock();
shutdownTimeout();
}
@ -702,6 +739,7 @@ void Servatrice::incRxBytes(quint64 num)
void Servatrice::shutdownTimeout()
{
QMutexLocker locker(&shutdownStateMutex);
// Show every time counter cut in half & every minute for last 5 minutes
if (shutdownMinutes <= 5 || shutdownMinutes == nextShutdownMessageMinutes) {
if (shutdownMinutes == nextShutdownMessageMinutes) {
@ -710,10 +748,7 @@ void Servatrice::shutdownTimeout()
SessionEvent *se;
if (shutdownMinutes) {
Event_ServerShutdown event;
event.set_reason(shutdownReason.toStdString());
event.set_minutes(static_cast<google::protobuf::uint32>(shutdownMinutes));
se = Server_ProtocolHandler::prepareSessionEvent(event);
se = makeShutdownEvent();
} else {
Event_ConnectionClosed event;
event.set_reason(Event_ConnectionClosed::SERVER_SHUTDOWN);

View file

@ -20,6 +20,7 @@
#ifndef SERVATRICE_H
#define SERVATRICE_H
#include <QDateTime>
#include <QHostAddress>
#include <QMetaType>
#include <QMutex>
@ -29,6 +30,8 @@
#include <QSslKey>
#include <QTcpServer>
#include <QWebSocketServer>
#include <libcockatrice/protocol/pb/response_report_stats.pb.h>
#include <memory>
#include <server.h>
#include <utility>
@ -158,6 +161,7 @@ private:
Servatrice_IslServer *islServer;
mutable QMutex loginMessageMutex;
QString loginMessage;
mutable QMutex shutdownStateMutex;
QString dbPrefix;
QMap<QString, bool> serverRequiredFeatureList;
QString officialWarnings;
@ -172,6 +176,11 @@ private:
int nextShutdownMessageMinutes;
QTimer *shutdownTimer;
mutable QMutex reportStatsMutex;
QDateTime reportStatsTimestamp;
std::shared_ptr<const Response_ReportStats> reportStatsCache;
static constexpr int reportStatsCacheTtlSeconds = 60;
mutable QMutex serverListMutex;
QList<ServerProperties> serverList;
void updateServerList();
@ -216,6 +225,8 @@ public:
QMutexLocker locker(&loginMessageMutex);
return loginMessage;
}
SessionEvent *getLoginSessionEvent() const override;
SessionEvent *makeShutdownEvent() const;
QString getRequiredFeatures() const override;
QString getAuthenticationMethodString() const;
QString getDBTypeString() const;
@ -280,6 +291,11 @@ public:
void removeIslInterface(int _serverId);
QReadWriteLock islLock;
// The moderation queue statistics are shared between all connected moderators and
// cached briefly to avoid re-running several full-table queries on every refresh.
std::shared_ptr<const Response_ReportStats> getCachedReportStats() const;
void cacheReportStats(const Response_ReportStats &stats);
QList<ServerProperties> getServerList() const;
};

View file

@ -14,6 +14,7 @@
#include <QSqlQuery>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/protocol/pb/game_replay.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/utility/passwordhasher.h>
inline Q_LOGGING_CATEGORY(DatabaseInterfaceLog, "database_interface");
@ -339,8 +340,8 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
return UserIsBanned;
}
QSqlQuery *passwordQuery =
prepareQuery("select password_sha512, active from {prefix}_users where name = :name");
QSqlQuery *passwordQuery = prepareQuery(
"select password_sha512, active, force_password_change from {prefix}_users where name = :name");
passwordQuery->bindValue(":name", user);
if (!execSqlQuery(passwordQuery)) {
qCWarning(DatabaseInterfaceLog) << "Login denied: SQL error";
@ -350,6 +351,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
if (passwordQuery->next()) {
const QString correctPasswordSha512 = passwordQuery->value(0).toString();
const bool userIsActive = passwordQuery->value(1).toBool();
const bool forceChange = passwordQuery->value(2).toBool();
if (!userIsActive) {
qCWarning(DatabaseInterfaceLog) << "Login denied: user not active";
return UserIsInactive;
@ -361,6 +363,10 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
hashedPassword = password;
}
if (correctPasswordSha512 == hashedPassword) {
if (forceChange) {
qCDebug(DatabaseInterfaceLog) << "Login accepted but password change required";
return PasswordChangeRequired;
}
qCDebug(DatabaseInterfaceLog) << "Login accepted: password right";
return PasswordRight;
} else {
@ -1084,11 +1090,22 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user,
"passwordLastChangedDate = NOW() where name = :name");
passwordQuery->bindValue(":password", passwordSha512);
passwordQuery->bindValue(":name", user);
if (execSqlQuery(passwordQuery)) {
return true;
if (!execSqlQuery(passwordQuery)) {
return false;
}
return passwordQuery->numRowsAffected() > 0;
}
void Servatrice_DatabaseInterface::setForcePasswordChange(const QString &user, bool force)
{
if (!checkSql()) {
return;
}
return false;
QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET force_password_change = :force WHERE name = :name");
query->bindValue(":force", force ? 1 : 0);
query->bindValue(":name", user);
execSqlQuery(query);
}
bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user,
@ -1314,6 +1331,169 @@ QList<ServerInfo_Warning> Servatrice_DatabaseInterface::getUserWarnHistory(const
return results;
}
QList<ServerInfo_UserSession> Servatrice_DatabaseInterface::getUserSessions(const QString &userName, int limit)
{
QList<ServerInfo_UserSession> results;
if (!checkSql()) {
return results;
}
QSqlQuery *query = prepareQuery("SELECT user_name, ip_address, clientid, "
"UNIX_TIMESTAMP(start_time), UNIX_TIMESTAMP(end_time), connection_type "
"FROM {prefix}_sessions WHERE user_name = :user_name "
"ORDER BY start_time DESC LIMIT :limit");
query->bindValue(":user_name", userName);
query->bindValue(":limit", limit);
if (!execSqlQuery(query)) {
qCWarning(DatabaseInterfaceLog) << "Failed to collect session history information: SQL Error";
return results;
}
while (query->next()) {
ServerInfo_UserSession sessionDetails;
sessionDetails.set_user_name(query->value(0).toString().toStdString());
sessionDetails.set_ip_address(query->value(1).toString().toStdString());
sessionDetails.set_clientid(query->value(2).toString().toStdString());
sessionDetails.set_start_time(query->value(3).toLongLong());
if (!query->value(4).isNull()) {
sessionDetails.set_end_time(query->value(4).toLongLong());
}
sessionDetails.set_connection_type(query->value(5).toString().toStdString());
results << sessionDetails;
}
return results;
}
QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QString &userName)
{
QList<ServerInfo_UserAlt> results;
if (!checkSql()) {
return results;
}
// Seed account identifiers used to find related accounts
QSqlQuery *seedQuery = prepareQuery("SELECT email, clientid FROM {prefix}_users WHERE name = :user_name");
seedQuery->bindValue(":user_name", userName);
if (!execSqlQuery(seedQuery) || !seedQuery->next()) {
return results;
}
const QString seedEmail = seedQuery->value(0).toString();
const QString seedClientId = seedQuery->value(1).toString();
QString queryString = "SELECT u.name, u.email, u.clientid, UNIX_TIMESTAMP(u.registrationDate), "
"UNIX_TIMESTAMP(a.last_login), "
"(SELECT COUNT(*) FROM {prefix}_warnings w WHERE w.user_id = u.id), "
"(SELECT COUNT(*) FROM {prefix}_bans b WHERE b.user_name = u.name), "
"u.active "
"FROM {prefix}_users u "
"LEFT JOIN {prefix}_user_analytics a ON a.id = u.id "
"WHERE u.name = :user_name";
if (!seedEmail.isEmpty()) {
queryString.append(" OR u.email = :seed_email");
}
if (!seedClientId.isEmpty()) {
queryString.append(" OR u.clientid = :seed_clientid");
}
queryString.append(" OR u.name IN (SELECT DISTINCT s.user_name FROM {prefix}_sessions s "
"WHERE s.ip_address IN (SELECT DISTINCT s2.ip_address FROM {prefix}_sessions s2 "
"WHERE s2.user_name = :user_name"
" AND s2.start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH))"
" AND s.start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH)) "
"ORDER BY u.name LIMIT 200");
QSqlQuery *query = prepareQuery(queryString);
query->bindValue(":user_name", userName);
if (!seedEmail.isEmpty()) {
query->bindValue(":seed_email", seedEmail);
}
if (!seedClientId.isEmpty()) {
query->bindValue(":seed_clientid", seedClientId);
}
if (!execSqlQuery(query)) {
qCWarning(DatabaseInterfaceLog) << "Failed to collect user alt information: SQL Error";
return results;
}
while (query->next()) {
ServerInfo_UserAlt altDetails;
altDetails.set_user_name(query->value(0).toString().toStdString());
altDetails.set_email(query->value(1).toString().toStdString());
altDetails.set_clientid(query->value(2).toString().toStdString());
altDetails.set_registration_time(query->value(3).toLongLong());
if (!query->value(4).isNull()) {
altDetails.set_last_login(query->value(4).toLongLong());
}
altDetails.set_warn_count(query->value(5).toInt());
altDetails.set_ban_count(query->value(6).toInt());
altDetails.set_is_active(query->value(7).toBool());
results << altDetails;
}
return results;
}
QList<ServerInfo_ModeratorLogin> Servatrice_DatabaseInterface::getModeratorLastLogins()
{
QList<ServerInfo_ModeratorLogin> results;
if (!checkSql()) {
return results;
}
QSqlQuery *query = prepareQuery("SELECT u.name, u.admin, UNIX_TIMESTAMP(a.last_login) "
"FROM {prefix}_users u "
"LEFT JOIN {prefix}_user_analytics a ON a.id = u.id "
"WHERE (u.admin & 7) <> 0 ORDER BY u.name");
if (!execSqlQuery(query)) {
qCWarning(DatabaseInterfaceLog) << "Failed to collect moderator login information: SQL Error";
return results;
}
while (query->next()) {
ServerInfo_ModeratorLogin loginDetails;
loginDetails.set_user_name(query->value(0).toString().toStdString());
const int isAdmin = query->value(1).toInt();
int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered;
if (isAdmin & 1) {
userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator;
} else if (isAdmin & 2) {
userLevel |= ServerInfo_User::IsModerator;
}
if (isAdmin & 4) {
userLevel |= ServerInfo_User::IsJudge;
}
loginDetails.set_user_level(userLevel);
if (!query->value(2).isNull()) {
loginDetails.set_last_login(query->value(2).toLongLong());
}
results << loginDetails;
}
return results;
}
bool Servatrice_DatabaseInterface::removeUserAvatar(const QString &userName)
{
if (!checkSql()) {
return false;
}
QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET avatar_bmp = '' WHERE name = :user_name");
query->bindValue(":user_name", userName);
if (!execSqlQuery(query)) {
return false;
}
return query->numRowsAffected() > 0;
}
QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory(const QString &user,
const QString &ipaddress,
const QString &gamename,

View file

@ -6,11 +6,14 @@
#include <QObject>
#include <QSqlDatabase>
#include <libcockatrice/protocol/pb/serverinfo_chat_message.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_moderator_login.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user_alt.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user_session.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_warning.pb.h>
#include <server.h>
#include <server_database_interface.h>
#define DATABASE_SCHEMA_VERSION 35
#define DATABASE_SCHEMA_VERSION 36
class Servatrice;
@ -119,6 +122,7 @@ public:
bool oldPasswordNeedsHash,
const QString &newPassword,
bool newPasswordNeedsHash) override;
void setForcePasswordChange(const QString &user, bool force) override;
QList<ServerInfo_Ban> getUserBanHistory(const QString userName);
bool
addWarning(const QString userName, const QString adminName, const QString warningReason, const QString clientID);
@ -133,6 +137,10 @@ public:
bool &room,
int &range,
int &maxresults);
QList<ServerInfo_UserSession> getUserSessions(const QString &userName, int limit);
QList<ServerInfo_UserAlt> getUserAlts(const QString &userName);
QList<ServerInfo_ModeratorLogin> getModeratorLastLogins();
bool removeUserAvatar(const QString &userName);
bool addForgotPassword(const QString &user);
bool removeForgotPassword(const QString &user) override;
bool doesForgotPasswordExist(const QString &user);

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,16 @@
#include <QMutex>
#include <QTcpSocket>
#include <QWebSocket>
#include <libcockatrice/protocol/pb/command_replay_download_by_game_id.pb.h>
#include <libcockatrice/protocol/pb/command_report.pb.h>
#include <libcockatrice/protocol/pb/command_report_add_comment.pb.h>
#include <libcockatrice/protocol/pb/command_report_assign.pb.h>
#include <libcockatrice/protocol/pb/command_report_details.pb.h>
#include <libcockatrice/protocol/pb/command_report_list.pb.h>
#include <libcockatrice/protocol/pb/command_report_my_list.pb.h>
#include <libcockatrice/protocol/pb/command_report_resolve.pb.h>
#include <libcockatrice/protocol/pb/command_report_stats.pb.h>
#include <libcockatrice/protocol/pb/command_report_user_info.pb.h>
#include <server_protocolhandler.h>
class Servatrice;
@ -50,6 +60,7 @@ class Command_BanFromServer;
class Command_UpdateServerMessage;
class Command_ShutdownServer;
class Command_ReloadConfig;
class Command_ReplayDownloadByGameId;
class Command_AccountEdit;
class Command_AccountImage;
@ -67,7 +78,7 @@ signals:
void incTxBytes(qint64 amount);
protected:
void logDebugMessage(const QString &message);
void logDebugMessage(const QString &message) override;
bool tooManyRegistrationAttempts(const QString &ipAddress);
virtual void writeToSocket(QByteArray &data) = 0;
@ -102,6 +113,7 @@ private:
Response::ResponseCode cmdReplayGetCode(const Command_ReplayGetCode &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReplaySubmitCode(const Command_ReplaySubmitCode &cmd, ResponseContainer &rc);
Response::ResponseCode cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReportList(const Command_ReportList &cmd, ResponseContainer &rc);
Response::ResponseCode cmdWarnUser(const Command_WarnUser &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGetLogHistory(const Command_ViewLogHistory &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGetBanHistory(const Command_GetBanHistory &cmd, ResponseContainer &rc);
@ -109,6 +121,10 @@ private:
Response::ResponseCode cmdGetWarnHistory(const Command_GetWarnHistory &cmd, ResponseContainer &rc);
Response::ResponseCode cmdShutdownServer(const Command_ShutdownServer &cmd, ResponseContainer &rc);
Response::ResponseCode cmdUpdateServerMessage(const Command_UpdateServerMessage &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReportAssign(const Command_ReportAssign &cmd, ResponseContainer &);
Response::ResponseCode cmdReportResolve(const Command_ReportResolve &cmd, ResponseContainer &);
Response::ResponseCode cmdReportUserInfo(const Command_ReportUserInfo &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReportStats(const Command_ReportStats &cmd, ResponseContainer &rc);
Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc);
Response::ResponseCode cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /* rc */);
Response::ResponseCode cmdReloadConfig(const Command_ReloadConfig & /* cmd */, ResponseContainer & /*rc*/);
@ -122,10 +138,19 @@ private:
Response::ResponseCode cmdForgotPasswordChallenge(const Command_ForgotPasswordChallenge &cmd,
ResponseContainer &rc);
Response::ResponseCode cmdRequestPasswordSalt(const Command_RequestPasswordSalt &cmd, ResponseContainer &rc);
Response::ResponseCode processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReport(const Command_Report &cmd, ResponseContainer &);
Response::ResponseCode cmdReportMyList(const Command_ReportMyList &cmd, ResponseContainer &rc);
void sendPendingReportNotifications(ResponseContainer &rc);
void onLogin(ResponseContainer &rc) override;
Response::ResponseCode cmdReportDetails(const Command_ReportDetails &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReportAddComment(const Command_ReportAddComment &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReplayDownloadByGameId(const Command_ReplayDownloadByGameId &cmd, ResponseContainer &rc);
Response::ResponseCode
processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc);
Response::ResponseCode processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc);
processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc) override;
Response::ResponseCode
processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc) override;
Response::ResponseCode
processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc) override;
Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc);
Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc);
@ -141,6 +166,12 @@ private:
Response::ResponseCode cmdGetAdminNotes(const Command_GetAdminNotes &cmd, ResponseContainer &rc);
Response::ResponseCode cmdUpdateAdminNotes(const Command_UpdateAdminNotes &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGetUserSessions(const Command_GetUserSessions &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGetUserAlts(const Command_GetUserAlts &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGetModeratorLastLogins(const Command_GetModeratorLastLogins &cmd, ResponseContainer &rc);
Response::ResponseCode cmdResetUserPassword(const Command_ResetUserPassword &cmd, ResponseContainer &rc);
Response::ResponseCode cmdRemoveUserAvatar(const Command_RemoveUserAvatar &cmd, ResponseContainer &rc);
bool addAdminFlagToUser(const QString &user, int flag);
bool removeAdminFlagFromUser(const QString &user, int flag);
@ -157,9 +188,9 @@ public:
bool initSession();
virtual QHostAddress getPeerAddress() const = 0;
virtual QString getAddress() const = 0;
QString getAddress() const override = 0;
void transmitProtocolItem(const ServerMessage &item);
void transmitProtocolItem(const ServerMessage &item) override;
};
class TcpServerSocketInterface : public AbstractServerSocketInterface