diff --git a/.gitignore b/.gitignore index 616ad480..78301957 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ tags +build *.qm +.directory diff --git a/cockatrice/src/abstractclient.h b/cockatrice/src/abstractclient.h index 19135c63..124f7a2f 100644 --- a/cockatrice/src/abstractclient.h +++ b/cockatrice/src/abstractclient.h @@ -38,7 +38,6 @@ class AbstractClient : public QObject { Q_OBJECT signals: void statusChanged(ClientStatus _status); - void serverError(Response::ResponseCode resp, QString reasonStr); // Room events void roomEventReceived(const RoomEvent &event); diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index 78420cbd..9ae607ca 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -24,6 +24,7 @@ RemoteClient::RemoteClient(QObject *parent) connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(slotSocketError(QAbstractSocket::SocketError))); connect(this, SIGNAL(serverIdentificationEventReceived(const Event_ServerIdentification &)), this, SLOT(processServerIdentificationEvent(const Event_ServerIdentification &))); + connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this, SLOT(processConnectionClosedEvent(Event_ConnectionClosed))); connect(this, SIGNAL(sigConnectToServer(QString, unsigned int, QString, QString)), this, SLOT(doConnectToServer(QString, unsigned int, QString, QString))); connect(this, SIGNAL(sigDisconnectFromServer()), this, SLOT(doDisconnectFromServer())); } @@ -66,6 +67,11 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica sendCommand(pend); } +void RemoteClient::processConnectionClosedEvent(const Event_ConnectionClosed & /*event*/) +{ + doDisconnectFromServer(); +} + void RemoteClient::loginResponse(const Response &response) { const Response_Login &resp = response.GetExtension(Response_Login::ext); @@ -83,7 +89,7 @@ void RemoteClient::loginResponse(const Response &response) ignoreList.append(resp.ignore_list(i)); emit ignoreListReceived(ignoreList); } else { - emit serverError(response.response_code(), QString::fromStdString(resp.denied_reason_str())); + emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), resp.denied_end_time()); setStatus(StatusDisconnecting); } } diff --git a/cockatrice/src/remoteclient.h b/cockatrice/src/remoteclient.h index 8f2e1b2b..050520cc 100644 --- a/cockatrice/src/remoteclient.h +++ b/cockatrice/src/remoteclient.h @@ -11,6 +11,7 @@ class RemoteClient : public AbstractClient { signals: void maxPingTime(int seconds, int maxSeconds); void serverTimeout(); + void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); void socketError(const QString &errorString); void protocolVersionMismatch(int clientVersion, int serverVersion); void protocolError(); @@ -22,6 +23,7 @@ private slots: void slotSocketError(QAbstractSocket::SocketError error); void ping(); void processServerIdentificationEvent(const Event_ServerIdentification &event); + void processConnectionClosedEvent(const Event_ConnectionClosed &event); void loginResponse(const Response &response); void doConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); void doDisconnectFromServer(); diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index f2ede16b..bd4a2db1 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "main.h" #include "window_main.h" @@ -67,6 +68,10 @@ void MainWindow::processConnectionClosedEvent(const Event_ConnectionClosed &even case Event_ConnectionClosed::TOO_MANY_CONNECTIONS: reasonStr = tr("There are too many concurrent connections from your address."); break; case Event_ConnectionClosed::BANNED: { reasonStr = tr("Banned by moderator"); + if (event.has_end_time()) + reasonStr.append("\n" + tr("Expected end time: %1").arg(QDateTime::fromTime_t(event.end_time()).toString())); + else + reasonStr.append("\n" + tr("This ban lasts indefinitely.")); if (event.has_reason_str()) reasonStr.append("\n\n" + QString::fromStdString(event.reason_str())); break; @@ -234,13 +239,29 @@ void MainWindow::serverTimeout() QMessageBox::critical(this, tr("Error"), tr("Server timeout")); } -void MainWindow::serverError(Response::ResponseCode r, QString reasonStr) +void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime) { switch (r) { - case Response::RespWrongPassword: QMessageBox::critical(this, tr("Error"), tr("Invalid login data.")); break; - case Response::RespWouldOverwriteOldSession: QMessageBox::critical(this, tr("Error"), tr("There is already an active session using this user name.\nPlease close that session first and re-login.")); break; - case Response::RespUserIsBanned: QMessageBox::critical(this, tr("Error"), tr("You are banned.\n%1").arg(reasonStr)); break; - default: QMessageBox::critical(this, tr("Error"), tr("Unknown server error: %1").arg(static_cast(r))); + case Response::RespWrongPassword: + QMessageBox::critical(this, tr("Error"), tr("Invalid login data.")); + break; + case Response::RespWouldOverwriteOldSession: + QMessageBox::critical(this, tr("Error"), tr("There is already an active session using this user name.\nPlease close that session first and re-login.")); + break; + case Response::RespUserIsBanned: { + QString bannedStr; + if (endTime) + bannedStr = tr("You are banned until %1.").arg(QDateTime::fromTime_t(endTime).toString()); + else + bannedStr = tr("You are banned indefinitely."); + if (!reasonStr.isEmpty()) + bannedStr.append("\n\n" + reasonStr); + + QMessageBox::critical(this, tr("Error"), bannedStr); + break; + } + default: + QMessageBox::critical(this, tr("Error"), tr("Unknown login error: %1").arg(static_cast(r))); } } @@ -343,7 +364,7 @@ MainWindow::MainWindow(QWidget *parent) client = new RemoteClient; connect(client, SIGNAL(connectionClosedEventReceived(const Event_ConnectionClosed &)), this, SLOT(processConnectionClosedEvent(const Event_ConnectionClosed &))); connect(client, SIGNAL(serverShutdownEventReceived(const Event_ServerShutdown &)), this, SLOT(processServerShutdownEvent(const Event_ServerShutdown &))); - connect(client, SIGNAL(serverError(Response::ResponseCode, QString)), this, SLOT(serverError(Response::ResponseCode, QString))); + connect(client, SIGNAL(loginError(Response::ResponseCode, QString, quint32)), this, SLOT(loginError(Response::ResponseCode, QString, quint32))); connect(client, SIGNAL(socketError(const QString &)), this, SLOT(socketError(const QString &))); connect(client, SIGNAL(serverTimeout()), this, SLOT(serverTimeout())); connect(client, SIGNAL(statusChanged(ClientStatus)), this, SLOT(statusChanged(ClientStatus))); diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index 6f06b38d..d6dd99a6 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -39,7 +39,7 @@ private slots: void processConnectionClosedEvent(const Event_ConnectionClosed &event); void processServerShutdownEvent(const Event_ServerShutdown &event); void serverTimeout(); - void serverError(Response::ResponseCode r, QString reasonStr); + void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime); void socketError(const QString &errorStr); void protocolVersionMismatch(int localVersion, int remoteVersion); void userInfoReceived(const ServerInfo_User &userInfo); diff --git a/cockatrice/translations/cockatrice_de.ts b/cockatrice/translations/cockatrice_de.ts index 0f6d075c..f5665671 100644 --- a/cockatrice/translations/cockatrice_de.ts +++ b/cockatrice/translations/cockatrice_de.ts @@ -2293,7 +2293,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic MainWindow - + There are too many concurrent connections from your address. Es gibt zu viele gleichzeitige Verbindungen von Ihrer Adresse. @@ -2302,7 +2302,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Gebannt von einem Moderator. - + Scheduled server shutdown. Planmäßige Serverabschaltung. @@ -2311,29 +2311,39 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Unbekannter Grund. - + Banned by moderator Gebannt von einem Moderator - + + Expected end time: %1 + Voraussichtliches Ende: %1 + + + + This ban lasts indefinitely. + Dieser Bann ist unbefristet. + + + Connection closed Verbindung geschlossen - + The server has terminated your connection. Reason: %1 Der Server hat Ihre Verbindung beendet. Grund: %1 - + Scheduled server shutdown Planmäßige Serverabschaltung - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2347,150 +2357,163 @@ Grund für die Abschaltung: %1 - + Number of players Spieleranzahl - + Please enter the number of players. Bitte die Spieleranzahl eingeben: - - + + Player %1 Spieler %1 - + Load replay Aufgezeichnetes Spiel laden - + About Cockatrice Über Cockatrice - + Version %1 Version %1 - + Authors: Autoren: - + Translators: Übersetzer: - + Spanish: Spanisch: - + Portugese (Portugal): Portugiesisch (Portugal): - + Portugese (Brazil): Portugiesisch (Brasilien): - + French: Französisch: - + Japanese: Japanisch: - + Russian: Russisch: - + Czech: Tschechisch: - + Italian: Italienisch: - + Swedish: Schwedisch: + + + You are banned until %1. + Sie sind gebannt bis: %1. + + + + You are banned indefinitely. + Sie sind auf unbestimmte Zeit gebannt. + + + + Unknown login error: %1 + Unbekannter Login-Fehler: %1 + Slovak: Slowakisch: - - - - - + + - - + + + + + Error Fehler - + Server timeout Server Zeitüberschreitung - + Invalid login data. Ungültige Anmeldedaten. - + There is already an active session using this user name. Please close that session first and re-login. Es gibt bereits eine aktive Verbindung mit diesem Benutzernamen. Bitte schließen Sie diese Verbindung zuerst und versuchen Sie es dann erneut. - You are banned. %1 - Sie sind gebannt. + Sie sind gebannt. %1 - Unknown server error: %1 - Unbekannter Serverfehler: %1 + Unbekannter Serverfehler: %1 - + Socket error: %1 Netzwerkfehler: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Sie versuchen sich an einem veralteten Server anzumelden. Bitte verwenden Sie eine ältere Cockatrice-Version oder melden Sie sich an einem aktuellen Server an. Lokale Version ist %1, Serverversion ist %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Ihr Cockatrice-Client ist veraltet. Bitte laden Sie sich die neueste Version herunter. @@ -2501,62 +2524,62 @@ Lokale Version ist %1, Serverversion ist %2. Protokollversionen stimmen nicht überein. Lokale Version: %1, Serverversion: %2. - + Connecting to %1... Verbinde zu %1... - + Disconnected nicht verbunden - + Connected, logging in at %1 Verbunden, Anmeldung bei %1 - + Logged in at %1 Angemeldet bei %1 - + &Connect... &Verbinden... - + &Disconnect Verbindung &trennen - + Start &local game... &Lokales Spiel starten... - + &Watch replay... &Aufgezeichnetes Spiel abspielen... - + &About Cockatrice &Über Cockatrice - + &Help &Hilfe - + Are you sure? Sind Sie sicher? - + There are still open games. Are you sure you want to quit? Es gibt noch offene Spiele. Wollen Sie das Programm wirklich beenden? @@ -2573,27 +2596,27 @@ Lokale Version ist %1, Serverversion ist %2. Spiel ver&lassen - + &Deck editor &Deck-Editor - + &Full screen &Vollbild - + Ctrl+F Ctrl+F - + &Settings... &Einstellungen... - + &Exit &Beenden @@ -2606,7 +2629,7 @@ Lokale Version ist %1, Serverversion ist %2. Esc - + &Cockatrice &Cockatrice @@ -5171,7 +5194,7 @@ Lokale Version ist %1, Serverversion ist %2. Alle Dateien (*.*) - + Cockatrice replays (*.cor) Aufgezeichnete Cockatrice-Spiele (*.cor) @@ -5197,32 +5220,32 @@ Lokale Version ist %1, Serverversion ist %2. RemoteReplayList_TreeModel - + ID ID - + Name Name - + Players Spieler - + Keep Behalten - + Time started Startzeit - + Duration (sec) Länge (Sekunden) diff --git a/common/pb/event_connection_closed.proto b/common/pb/event_connection_closed.proto index 9908c84e..55ea954f 100644 --- a/common/pb/event_connection_closed.proto +++ b/common/pb/event_connection_closed.proto @@ -12,4 +12,5 @@ message Event_ConnectionClosed { } optional CloseReason reason = 1; optional string reason_str = 2; + optional uint32 end_time = 3; } diff --git a/common/pb/response_login.proto b/common/pb/response_login.proto index f79b4194..a554ca09 100644 --- a/common/pb/response_login.proto +++ b/common/pb/response_login.proto @@ -9,4 +9,5 @@ message Response_Login { repeated ServerInfo_User buddy_list = 2; repeated ServerInfo_User ignore_list = 3; optional string denied_reason_str = 4; + optional uint64 denied_end_time = 5; } diff --git a/common/server.cpp b/common/server.cpp index 49e36d8b..19b67b14 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -66,14 +66,14 @@ void Server::prepareDestroy() roomsLock.unlock(); } -AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reasonStr) +AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reasonStr, int &secondsLeft) { if (name.size() > 35) name = name.left(35); QWriteLocker locker(&clientsLock); - AuthenticationResult authState = checkUserPassword(session, name, password, reasonStr); + AuthenticationResult authState = checkUserPassword(session, name, password, reasonStr, secondsLeft); if ((authState == NotLoggedIn) || (authState == UserIsBanned)) return authState; diff --git a/common/server.h b/common/server.h index f14959b0..c69bc173 100644 --- a/common/server.h +++ b/common/server.h @@ -41,7 +41,7 @@ public: mutable QReadWriteLock clientsLock, roomsLock; // locking order: roomsLock before clientsLock Server(QObject *parent = 0); ~Server(); - AuthenticationResult loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reason); + AuthenticationResult loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reason, int &secondsLeft); const QMap &getRooms() { return rooms; } virtual int getNextGameId() { return nextGameId++; } virtual int getNextReplayId() { return nextReplayId++; } @@ -111,7 +111,7 @@ protected: virtual qint64 startSession(const QString &userName, const QString &address) { return 0; } virtual void endSession(qint64 sessionId) { } virtual bool userExists(const QString &user) { return false; } - virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reason) { return UnknownUser; } + virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reason, int &secondsLeft) { return UnknownUser; } virtual ServerInfo_User getUserData(const QString &name, bool withId = false) = 0; int getUsersCount() const; int getGamesCount() const; diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 18c7710b..416e5123 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -337,11 +337,14 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd if (userName.isEmpty() || (userInfo != 0)) return Response::RespContextError; QString reasonStr; - AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr); + int banSecondsLeft = 0; + AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr, banSecondsLeft); switch (res) { case UserIsBanned: { Response_Login *re = new Response_Login; re->set_denied_reason_str(reasonStr.toStdString()); + if (banSecondsLeft != 0) + re->set_denied_end_time(QDateTime::currentDateTime().addSecs(banSecondsLeft).toTime_t()); rc.setResponseExtension(re); return Response::RespUserIsBanned; } diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index e49f4c88..38c0c0a5 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -297,7 +297,7 @@ QList Servatrice::getServerList() const return result; } -AuthenticationResult Servatrice::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr) +AuthenticationResult Servatrice::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &banSecondsLeft) { QMutexLocker locker(&dbMutex); const QString method = settings->value("authentication/method").toString(); @@ -308,7 +308,7 @@ AuthenticationResult Servatrice::checkUserPassword(Server_ProtocolHandler *handl return UnknownUser; QSqlQuery ipBanQuery; - ipBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))) < 0, b.minutes <=> 0, b.visible_reason from " + dbPrefix + "_bans b where b.time_from = (select max(c.time_from) from " + dbPrefix + "_bans c where c.ip_address = :address) and b.ip_address = :address2"); + ipBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from " + dbPrefix + "_bans b where b.time_from = (select max(c.time_from) from " + dbPrefix + "_bans c where c.ip_address = :address) and b.ip_address = :address2"); ipBanQuery.bindValue(":address", static_cast(handler)->getPeerAddress().toString()); ipBanQuery.bindValue(":address2", static_cast(handler)->getPeerAddress().toString()); if (!execSqlQuery(ipBanQuery)) { @@ -316,15 +316,19 @@ AuthenticationResult Servatrice::checkUserPassword(Server_ProtocolHandler *handl return NotLoggedIn; } - if (ipBanQuery.next()) - if (ipBanQuery.value(0).toInt() || ipBanQuery.value(1).toInt()) { + if (ipBanQuery.next()) { + const int secondsLeft = -ipBanQuery.value(0).toInt(); + const bool permanentBan = ipBanQuery.value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { reasonStr = ipBanQuery.value(2).toString(); + banSecondsLeft = permanentBan ? 0 : secondsLeft; qDebug("Login denied: banned by address"); return UserIsBanned; } + } QSqlQuery nameBanQuery; - nameBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))) < 0, b.minutes <=> 0, b.visible_reason from " + dbPrefix + "_bans b where b.time_from = (select max(c.time_from) from " + dbPrefix + "_bans c where c.user_name = :name2) and b.user_name = :name1"); + nameBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from " + dbPrefix + "_bans b where b.time_from = (select max(c.time_from) from " + dbPrefix + "_bans c where c.user_name = :name2) and b.user_name = :name1"); nameBanQuery.bindValue(":name1", user); nameBanQuery.bindValue(":name2", user); if (!execSqlQuery(nameBanQuery)) { @@ -332,12 +336,16 @@ AuthenticationResult Servatrice::checkUserPassword(Server_ProtocolHandler *handl return NotLoggedIn; } - if (nameBanQuery.next()) - if (nameBanQuery.value(0).toInt() || nameBanQuery.value(1).toInt()) { + if (nameBanQuery.next()) { + const int secondsLeft = -nameBanQuery.value(0).toInt(); + const bool permanentBan = nameBanQuery.value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { reasonStr = nameBanQuery.value(2).toString(); + banSecondsLeft = permanentBan ? 0 : secondsLeft; qDebug("Login denied: banned by name"); return UserIsBanned; } + } QSqlQuery passwordQuery; passwordQuery.prepare("select password_sha512 from " + dbPrefix + "_users where name = :name and active = 1"); diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index 4411c3dc..5fbeefab 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -130,7 +130,7 @@ protected: qint64 startSession(const QString &userName, const QString &address); void endSession(qint64 sessionId); bool userExists(const QString &user); - AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr); + AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); void clearSessionTables(); void lockSessionTables(); diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 869ef555..cd92a2e5 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -587,8 +587,8 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban query.bindValue(":ip_address", address); query.bindValue(":id_admin", userInfo->id()); query.bindValue(":minutes", minutes); - query.bindValue(":reason", QString::fromStdString(cmd.reason()) + "\n"); - query.bindValue(":visible_reason", QString::fromStdString(cmd.visible_reason()) + "\n"); + query.bindValue(":reason", QString::fromStdString(cmd.reason())); + query.bindValue(":visible_reason", QString::fromStdString(cmd.visible_reason())); servatrice->execSqlQuery(query); servatrice->dbMutex.unlock(); @@ -601,6 +601,8 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban event.set_reason(Event_ConnectionClosed::BANNED); if (cmd.has_visible_reason()) event.set_reason_str(cmd.visible_reason()); + if (minutes) + event.set_end_time(QDateTime::currentDateTime().addSecs(60 * minutes).toTime_t()); for (int i = 0; i < userList.size(); ++i) { SessionEvent *se = userList[i]->prepareSessionEvent(event); userList[i]->sendProtocolItem(*se);