#include "servatrice.h" #include "servatrice_database_interface.h" #include "passwordhasher.h" #include "serversocketinterface.h" #include "decklist.h" #include "pb/game_replay.pb.h" #include #include #include Servatrice_DatabaseInterface::Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server) : instanceId(_instanceId), sqlDatabase(QSqlDatabase()), server(_server) { } Servatrice_DatabaseInterface::~Servatrice_DatabaseInterface() { sqlDatabase.close(); } void Servatrice_DatabaseInterface::initDatabase(const QSqlDatabase &_sqlDatabase) { if (_sqlDatabase.isValid()) { sqlDatabase = QSqlDatabase::cloneDatabase(_sqlDatabase, "pool_" + QString::number(instanceId)); openDatabase(); } } void Servatrice_DatabaseInterface::initDatabase(const QString &type, const QString &hostName, const QString &databaseName, const QString &userName, const QString &password) { sqlDatabase = QSqlDatabase::addDatabase(type, "main"); sqlDatabase.setHostName(hostName); sqlDatabase.setDatabaseName(databaseName); sqlDatabase.setUserName(userName); sqlDatabase.setPassword(password); openDatabase(); } bool Servatrice_DatabaseInterface::openDatabase() { if (sqlDatabase.isOpen()) sqlDatabase.close(); const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); qDebug() << QString("[%1] Opening database...").arg(poolStr); if (!sqlDatabase.open()) { qCritical() << QString("[%1] Error opening database: %2").arg(poolStr).arg(sqlDatabase.lastError().text()); return false; } return true; } bool Servatrice_DatabaseInterface::checkSql() { if (!sqlDatabase.isValid()) return false; if (!sqlDatabase.exec("select 1").isActive()) return openDatabase(); return true; } bool Servatrice_DatabaseInterface::execSqlQuery(QSqlQuery &query) { if (query.exec()) return true; const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); qCritical() << QString("[%1] Error executing query: %2").arg(poolStr).arg(query.lastError().text()); return false; } AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &banSecondsLeft) { switch (server->getAuthenticationMethod()) { case Servatrice::AuthenticationNone: return UnknownUser; case Servatrice::AuthenticationSql: { if (!checkSql()) return UnknownUser; QSqlQuery ipBanQuery(sqlDatabase); ipBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from " + server->getDbPrefix() + "_bans b where b.time_from = (select max(c.time_from) from " + server->getDbPrefix() + "_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)) { qDebug("Login denied: SQL error"); return NotLoggedIn; } 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(sqlDatabase); nameBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from " + server->getDbPrefix() + "_bans b where b.time_from = (select max(c.time_from) from " + server->getDbPrefix() + "_bans c where c.user_name = :name2) and b.user_name = :name1"); nameBanQuery.bindValue(":name1", user); nameBanQuery.bindValue(":name2", user); if (!execSqlQuery(nameBanQuery)) { qDebug("Login denied: SQL error"); return NotLoggedIn; } 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(sqlDatabase); passwordQuery.prepare("select password_sha512 from " + server->getDbPrefix() + "_users where name = :name and active = 1"); passwordQuery.bindValue(":name", user); if (!execSqlQuery(passwordQuery)) { qDebug("Login denied: SQL error"); return NotLoggedIn; } if (passwordQuery.next()) { const QString correctPassword = passwordQuery.value(0).toString(); if (correctPassword == PasswordHasher::computeHash(password, correctPassword.left(16))) { qDebug("Login accepted: password right"); return PasswordRight; } else { qDebug("Login denied: password wrong"); return NotLoggedIn; } } else { qDebug("Login accepted: unknown user"); return UnknownUser; } } } return UnknownUser; } bool Servatrice_DatabaseInterface::userExists(const QString &user) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); QSqlQuery query(sqlDatabase); query.prepare("select 1 from " + server->getDbPrefix() + "_users where name = :name and active = 1"); query.bindValue(":name", user); if (!execSqlQuery(query)) return false; return query.next(); } return false; } int Servatrice_DatabaseInterface::getUserIdInDB(const QString &name) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { QSqlQuery query(sqlDatabase); query.prepare("select id from " + server->getDbPrefix() + "_users where name = :name and active = 1"); query.bindValue(":name", name); if (!execSqlQuery(query)) return -1; if (!query.next()) return -1; return query.value(0).toInt(); } return -1; } bool Servatrice_DatabaseInterface::isInBuddyList(const QString &whoseList, const QString &who) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) return false; if (!checkSql()) return false; int id1 = getUserIdInDB(whoseList); int id2 = getUserIdInDB(who); QSqlQuery query(sqlDatabase); query.prepare("select 1 from " + server->getDbPrefix() + "_buddylist where id_user1 = :id_user1 and id_user2 = :id_user2"); query.bindValue(":id_user1", id1); query.bindValue(":id_user2", id2); if (!execSqlQuery(query)) return false; return query.next(); } bool Servatrice_DatabaseInterface::isInIgnoreList(const QString &whoseList, const QString &who) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) return false; if (!checkSql()) return false; int id1 = getUserIdInDB(whoseList); int id2 = getUserIdInDB(who); QSqlQuery query(sqlDatabase); query.prepare("select 1 from " + server->getDbPrefix() + "_ignorelist where id_user1 = :id_user1 and id_user2 = :id_user2"); query.bindValue(":id_user1", id1); query.bindValue(":id_user2", id2); if (!execSqlQuery(query)) return false; return query.next(); } ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuery &query, bool complete, bool withId) { ServerInfo_User result; if (withId) result.set_id(query.value(0).toInt()); result.set_name(query.value(1).toString().toStdString()); const QString country = query.value(5).toString(); if (!country.isEmpty()) result.set_country(country.toStdString()); if (complete) { const QByteArray avatarBmp = query.value(6).toByteArray(); if (avatarBmp.size()) result.set_avatar_bmp(avatarBmp.data(), avatarBmp.size()); } const QString genderStr = query.value(4).toString(); if (genderStr == "m") result.set_gender(ServerInfo_User::Male); else if (genderStr == "f") result.set_gender(ServerInfo_User::Female); const int is_admin = query.value(2).toInt(); int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered; if (is_admin == 1) userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator; else if (is_admin == 2) userLevel |= ServerInfo_User::IsModerator; result.set_user_level(userLevel); const QString realName = query.value(3).toString(); if (!realName.isEmpty()) result.set_real_name(realName.toStdString()); return result; } ServerInfo_User Servatrice_DatabaseInterface::getUserData(const QString &name, bool withId) { ServerInfo_User result; result.set_name(name.toStdString()); result.set_user_level(ServerInfo_User::IsUser); if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { if (!checkSql()) return result; QSqlQuery query(sqlDatabase); query.prepare("select id, name, admin, realname, gender, country, avatar_bmp from " + server->getDbPrefix() + "_users where name = :name and active = 1"); query.bindValue(":name", name); if (!execSqlQuery(query)) return result; if (query.next()) return evalUserQueryResult(query, true, withId); else return result; } else return result; } void Servatrice_DatabaseInterface::clearSessionTables() { lockSessionTables(); QSqlQuery query(sqlDatabase); query.prepare("update " + server->getDbPrefix() + "_sessions set end_time=now() where end_time is null and id_server = :id_server"); query.bindValue(":id_server", server->getServerId()); query.exec(); unlockSessionTables(); } void Servatrice_DatabaseInterface::lockSessionTables() { QSqlQuery("lock tables " + server->getDbPrefix() + "_sessions write, " + server->getDbPrefix() + "_users read", sqlDatabase).exec(); } void Servatrice_DatabaseInterface::unlockSessionTables() { QSqlQuery("unlock tables", sqlDatabase).exec(); } bool Servatrice_DatabaseInterface::userSessionExists(const QString &userName) { // Call only after lockSessionTables(). QSqlQuery query(sqlDatabase); query.prepare("select 1 from " + server->getDbPrefix() + "_sessions where user_name = :user_name and end_time is null"); query.bindValue(":user_name", userName); query.exec(); return query.next(); } qint64 Servatrice_DatabaseInterface::startSession(const QString &userName, const QString &address) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) return -1; if (!checkSql()) return -1; QSqlQuery query(sqlDatabase); query.prepare("insert into " + server->getDbPrefix() + "_sessions (user_name, id_server, ip_address, start_time) values(:user_name, :id_server, :ip_address, NOW())"); query.bindValue(":user_name", userName); query.bindValue(":id_server", server->getServerId()); query.bindValue(":ip_address", address); if (execSqlQuery(query)) return query.lastInsertId().toInt(); return -1; } void Servatrice_DatabaseInterface::endSession(qint64 sessionId) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) return; if (!checkSql()) return; QSqlQuery query(sqlDatabase); query.exec("lock tables " + server->getDbPrefix() + "_sessions write"); query.prepare("update " + server->getDbPrefix() + "_sessions set end_time=NOW() where id = :id_session"); query.bindValue(":id_session", sessionId); execSqlQuery(query); query.exec("unlock tables"); } QMap Servatrice_DatabaseInterface::getBuddyList(const QString &name) { QMap result; if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); QSqlQuery query(sqlDatabase); query.prepare("select a.id, a.name, a.admin, a.realname, a.gender, a.country from " + server->getDbPrefix() + "_users a left join " + server->getDbPrefix() + "_buddylist b on a.id = b.id_user2 left join " + server->getDbPrefix() + "_users c on b.id_user1 = c.id where c.name = :name"); query.bindValue(":name", name); if (!execSqlQuery(query)) return result; while (query.next()) { const ServerInfo_User &temp = evalUserQueryResult(query, false); result.insert(QString::fromStdString(temp.name()), temp); } } return result; } QMap Servatrice_DatabaseInterface::getIgnoreList(const QString &name) { QMap result; if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); QSqlQuery query(sqlDatabase); query.prepare("select a.id, a.name, a.admin, a.realname, a.gender, a.country from " + server->getDbPrefix() + "_users a left join " + server->getDbPrefix() + "_ignorelist b on a.id = b.id_user2 left join " + server->getDbPrefix() + "_users c on b.id_user1 = c.id where c.name = :name"); query.bindValue(":name", name); if (!execSqlQuery(query)) return result; while (query.next()) { ServerInfo_User temp = evalUserQueryResult(query, false); result.insert(QString::fromStdString(temp.name()), temp); } } return result; } int Servatrice_DatabaseInterface::getNextGameId() { if (!sqlDatabase.isValid()) return server->getNextLocalGameId(); if (!checkSql()) return -1; QSqlQuery query(sqlDatabase); query.prepare("insert into " + server->getDbPrefix() + "_games (time_started) values (now())"); execSqlQuery(query); return query.lastInsertId().toInt(); } int Servatrice_DatabaseInterface::getNextReplayId() { if (!checkSql()) return -1; QSqlQuery query(sqlDatabase); query.prepare("insert into " + server->getDbPrefix() + "_replays () values ()"); execSqlQuery(query); return query.lastInsertId().toInt(); } void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, const QStringList &roomGameTypes, const ServerInfo_Game &gameInfo, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const QList &replayList) { if (!checkSql()) return; QVariantList gameIds1, playerNames, gameIds2, userIds, replayNames; QSetIterator playerIterator(allPlayersEver); while (playerIterator.hasNext()) { gameIds1.append(gameInfo.game_id()); const QString &playerName = playerIterator.next(); playerNames.append(playerName); } QSet allUsersInGame = allPlayersEver + allSpectatorsEver; QSetIterator allUsersIterator(allUsersInGame); while (allUsersIterator.hasNext()) { int id = getUserIdInDB(allUsersIterator.next()); if (id == -1) continue; gameIds2.append(gameInfo.game_id()); userIds.append(id); replayNames.append(QString::fromStdString(gameInfo.description())); } QVariantList replayIds, replayGameIds, replayDurations, replayBlobs; for (int i = 0; i < replayList.size(); ++i) { QByteArray blob; const unsigned int size = replayList[i]->ByteSize(); blob.resize(size); replayList[i]->SerializeToArray(blob.data(), size); replayIds.append(QVariant((qulonglong) replayList[i]->replay_id())); replayGameIds.append(gameInfo.game_id()); replayDurations.append(replayList[i]->duration_seconds()); replayBlobs.append(blob); } { QSqlQuery query(sqlDatabase); query.prepare("update " + server->getDbPrefix() + "_games set room_name=:room_name, descr=:descr, creator_name=:creator_name, password=:password, game_types=:game_types, player_count=:player_count, time_finished=now() where id=:id_game"); query.bindValue(":room_name", roomName); query.bindValue(":id_game", gameInfo.game_id()); query.bindValue(":descr", QString::fromStdString(gameInfo.description())); query.bindValue(":creator_name", QString::fromStdString(gameInfo.creator_info().name())); query.bindValue(":password", gameInfo.with_password() ? 1 : 0); query.bindValue(":game_types", roomGameTypes.isEmpty() ? QString("") : roomGameTypes.join(", ")); query.bindValue(":player_count", gameInfo.max_players()); if (!execSqlQuery(query)) return; } { QSqlQuery query(sqlDatabase); query.prepare("insert into " + server->getDbPrefix() + "_games_players (id_game, player_name) values (:id_game, :player_name)"); query.bindValue(":id_game", gameIds1); query.bindValue(":player_name", playerNames); query.execBatch(); } { QSqlQuery query(sqlDatabase); query.prepare("update " + server->getDbPrefix() + "_replays set id_game=:id_game, duration=:duration, replay=:replay where id=:id_replay"); query.bindValue(":id_replay", replayIds); query.bindValue(":id_game", replayGameIds); query.bindValue(":duration", replayDurations); query.bindValue(":replay", replayBlobs); query.execBatch(); } { QSqlQuery query(sqlDatabase); query.prepare("insert into " + server->getDbPrefix() + "_replays_access (id_game, id_player, replay_name) values (:id_game, :id_player, :replay_name)"); query.bindValue(":id_game", gameIds2); query.bindValue(":id_player", userIds); query.bindValue(":replay_name", replayNames); query.execBatch(); } } DeckList *Servatrice_DatabaseInterface::getDeckFromDatabase(int deckId, const QString &userName) { checkSql(); QSqlQuery query(sqlDatabase); query.prepare("select content from " + server->getDbPrefix() + "_decklist_files where id = :id and user = :user"); query.bindValue(":id", deckId); query.bindValue(":user", userName); execSqlQuery(query); if (!query.next()) throw Response::RespNameNotFound; DeckList *deck = new DeckList; deck->loadFromString_Native(query.value(0).toString()); return deck; }