Generate passphrase for save-code

* when none is given
* if the given save-code already exist
* if the database connection fails therefore it can't be checked if the save-code already exists

Using the short word list from eff:
https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases
This commit is contained in:
Zwelf 2020-06-10 22:58:49 +02:00
parent 528ca377ed
commit ea31171873
6 changed files with 1436 additions and 49 deletions

1296
data/wordlist.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -30,6 +30,7 @@ public:
const char* GetPass() { return m_aPass; }
const char* GetIP() { return m_aIp; }
int GetPort() { return m_Port; }
sql::Connection *Connection() const { return m_pConnection; }
static int ms_NumReadServer;
static int ms_NumWriteServer;

View file

@ -693,17 +693,20 @@ void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData)
if(pPlayer->m_LastSQLQuery + g_Config.m_SvSqlQueriesDelay * pSelf->Server()->TickSpeed() >= pSelf->Server()->Tick())
return;
const char* pCode = pResult->GetString(0);
const char* pCode = "";
if(pResult->NumArguments() > 0)
pCode = pResult->GetString(0);
char aCountry[5];
if(str_length(pCode) > 3 && pCode[0] >= 'A' && pCode[0] <= 'Z' && pCode[1] >= 'A'
if(str_length(pCode) >= 3 && pCode[0] >= 'A' && pCode[0] <= 'Z' && pCode[1] >= 'A'
&& pCode[1] <= 'Z' && pCode[2] >= 'A' && pCode[2] <= 'Z')
{
if(pCode[3] == ' ')
if(str_length(pCode) == 3 || pCode[3] == ' ')
{
str_copy(aCountry, pCode, 4);
pCode = str_skip_whitespaces_const(pCode + 4);
}
else if(str_length(pCode) > 4 && pCode[4] == ' ')
else if(str_length(pCode) == 4 || (str_length(pCode) > 4 && pCode[4] == ' '))
{
str_copy(aCountry, pCode, 5);
pCode = str_skip_whitespaces_const(pCode + 5);

View file

@ -27,7 +27,7 @@ CHAT_COMMAND("dnd", "", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConD
CHAT_COMMAND("mapinfo", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConMapInfo, this, "Show info about the map with name r gives (current map by default)")
CHAT_COMMAND("timeout", "?s[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTimeout, this, "Set timeout protection code s")
CHAT_COMMAND("practice", "?i['0'|'1']", CFGFLAG_CHAT|CFGFLAG_SERVER, ConPractice, this, "Enable cheats (currently only /rescue) for your current team's run, but you can't earn a rank")
CHAT_COMMAND("save", "r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConSave, this, "Save team with code r to current server. To save to another server, use '/save s r' where s = server (case-sensitive: GER, RUS, etc) and r = code.")
CHAT_COMMAND("save", "?r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConSave, this, "Save team with code r to current server. To save to another server, use '/save s r' where s = server (case-sensitive: GER, RUS, etc) and r = code.")
CHAT_COMMAND("load", "?r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConLoad, this, "Load with code r. /load to check your existing saves")
CHAT_COMMAND("map", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConMap, this, "Vote a map by name")
CHAT_COMMAND("rankteam", "?r[player name]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTeamRank, this, "Shows the team rank of player with name r (your team rank by default)")

View file

@ -6,11 +6,14 @@
#include <fstream>
#include <cstring>
#include <random>
#include <cppconn/prepared_statement.h>
#include <base/system.h>
#include <engine/server/sql_connector.h>
#include <engine/shared/config.h>
#include <engine/shared/console.h>
#include <engine/shared/linereader.h>
#include <engine/storage.h>
#include <algorithm>
@ -100,6 +103,18 @@ void CSqlScore::ExecPlayerThread(
pThreadName);
}
void CSqlScore::GeneratePassphrase(char *pBuf, int BufSize)
{
for(int i = 0; i < 3; i++)
{
if(i != 0)
str_append(pBuf, " ", BufSize);
// TODO: decide if the slight bias towards lower numbers is ok
int Rand = m_Prng.RandomBits() % m_aWordlist.size();
str_append(pBuf, m_aWordlist[Rand].c_str(), BufSize);
}
}
LOCK CSqlScore::ms_FailureFileLock = lock_create();
void CSqlScore::OnShutdown()
@ -169,6 +184,33 @@ CSqlScore::CSqlScore(CGameContext *pGameServer) :
((CGameControllerDDRace*)(pGameServer->m_pController))->m_pInitResult = InitResult;
Tmp->m_Map = g_Config.m_SvMap;
IOHANDLE File = GameServer()->Storage()->OpenFile("wordlist.txt", IOFLAG_READ, IStorage::TYPE_ALL);
if(!File)
{
dbg_msg("sql", "failed to open wordlist");
Server()->SetErrorShutdown("sql open wordlist error");
return;
}
uint64 aSeed[2];
secure_random_fill(aSeed, sizeof(aSeed));
m_Prng.Seed(aSeed);
CLineReader LineReader;
LineReader.Init(File);
char *pLine;
while((pLine = LineReader.Get()))
{
char Word[32] = {0};
sscanf(pLine, "%*s %31s", Word);
Word[31] = 0;
m_aWordlist.push_back(Word);
}
if(m_aWordlist.size() < 1000)
{
dbg_msg("sql", "too few words in wordlist");
Server()->SetErrorShutdown("sql too few words in wordlist");
return;
}
thread_init_and_detach(CSqlExecData<CSqlInitResult>::ExecSqlFunc,
new CSqlExecData<CSqlInitResult>(Init, Tmp),
"SqlScore constructor");
@ -1463,11 +1505,13 @@ void CSqlScore::SaveTeam(int ClientID, const char* Code, const char* Server)
pController->m_Teams.SetSaving(Team, SaveResult);
CSqlTeamSave *Tmp = new CSqlTeamSave(SaveResult);
Tmp->m_Code = Code;
Tmp->m_Map = g_Config.m_SvMap;
str_copy(Tmp->m_Code, Code, sizeof(Tmp->m_Code));
str_copy(Tmp->m_Map, g_Config.m_SvMap, sizeof(Tmp->m_Map));
Tmp->m_pResult->m_SaveID = RandomUuid();
str_copy(Tmp->m_Server, Server, sizeof(Tmp->m_Server));
str_copy(Tmp->m_ClientName, this->Server()->ClientName(ClientID), sizeof(Tmp->m_ClientName));
Tmp->m_aGeneratedCode[0] = '\0';
GeneratePassphrase(Tmp->m_aGeneratedCode, sizeof(Tmp->m_aGeneratedCode));
pController->m_Teams.KillSavedTeam(ClientID, Team);
char aBuf[512];
@ -1487,7 +1531,7 @@ bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData<CSqlSaveRe
char aSaveID[UUID_MAXSTRSIZE];
FormatUuid(pData->m_pResult->m_SaveID, aSaveID, UUID_MAXSTRSIZE);
sqlstr::CSqlString<65536> SaveState = pData->m_pResult->m_SavedTeam.GetString();
char *pSaveState = pData->m_pResult->m_SavedTeam.GetString();
if(HandleFailure)
{
if (!g_Config.m_SvSqlFailureFile[0])
@ -1498,13 +1542,16 @@ bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData<CSqlSaveRe
if(File)
{
dbg_msg("sql", "ERROR: Could not save Teamsave, writing insert to a file now...");
sqlstr::CSqlString<65536> SaveState = pSaveState;
sqlstr::CSqlString<128> Code = pData->m_aGeneratedCode;
sqlstr::CSqlString<128> Map = pData->m_Map;
char aBuf[65536];
str_format(aBuf, sizeof(aBuf),
"INSERT IGNORE INTO %%s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) "
"VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s', '%s', false)",
SaveState.ClrStr(), pData->m_Map.ClrStr(),
pData->m_Code.ClrStr(), pData->m_Server, aSaveID
SaveState.ClrStr(), Map.ClrStr(),
Code.ClrStr(), pData->m_Server, aSaveID
);
io_write(File, aBuf, str_length(aBuf));
io_write_newline(File);
@ -1514,63 +1561,98 @@ bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData<CSqlSaveRe
pData->m_pResult->m_Status = CSqlSaveResult::SAVE_SUCCESS;
strcpy(pData->m_pResult->m_aBroadcast,
"Database connection failed, teamsave written to a file instead. Admins will add it manually in a few days.");
str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage),
"Team successfully saved by %s. Use '/load %s' to continue",
pData->m_ClientName, Code.Str());
return true;
}
lock_unlock(ms_FailureFileLock);
dbg_msg("sql", "ERROR: Could not save Teamsave, NOT even to a file");
return false;
}
else
try
{
try
char aBuf[65536];
str_format(aBuf, sizeof(aBuf), "lock tables %s_saves write;", pSqlServer->GetPrefix());
pSqlServer->executeSql(aBuf);
char Code[128] = {0};
str_format(aBuf, sizeof(aBuf), "SELECT Savegame FROM %s_saves WHERE Code = ? AND Map = ?", pSqlServer->GetPrefix());
std::unique_ptr<sql::PreparedStatement> pPrepStmt;
std::unique_ptr<sql::ResultSet> pResult;
pPrepStmt.reset(pSqlServer->Connection()->prepareStatement(aBuf));
bool UseCode = false;
if(pData->m_Code[0] != '\0')
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "lock tables %s_saves write;", pSqlServer->GetPrefix());
pSqlServer->executeSql(aBuf);
str_format(aBuf, sizeof(aBuf),
"SELECT Savegame "
"FROM %s_saves "
"WHERE Code = '%s' AND Map = '%s';",
pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr());
pSqlServer->executeSqlQuery(aBuf);
if (pSqlServer->GetResults()->rowsCount() == 0)
pPrepStmt->setString(1, pData->m_Code);
pPrepStmt->setString(2, pData->m_Map);
pResult.reset(pPrepStmt->executeQuery());
if(pResult->rowsCount() == 0)
{
char aBuf[65536];
UseCode = true;
str_copy(Code, pData->m_Code, sizeof(Code));
}
}
if(!UseCode)
{
// use random generated passphrase if save code exists or no save code given
pPrepStmt->setString(1, pData->m_aGeneratedCode);
pPrepStmt->setString(2, pData->m_Map);
pResult.reset(pPrepStmt->executeQuery());
if(pResult->rowsCount() == 0)
{
UseCode = true;
str_copy(Code, pData->m_aGeneratedCode, sizeof(Code));
}
}
str_format(aBuf, sizeof(aBuf),
"INSERT IGNORE INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) "
"VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s', '%s', false)",
pSqlServer->GetPrefix(), SaveState.ClrStr(), pData->m_Map.ClrStr(),
pData->m_Code.ClrStr(), pData->m_Server, aSaveID
);
dbg_msg("sql", "%s", aBuf);
pSqlServer->executeSql(aBuf);
if(UseCode)
{
str_format(aBuf, sizeof(aBuf),
"INSERT IGNORE INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) "
"VALUES (?, ?, ?, CURRENT_TIMESTAMP(), ?, ?, false)",
pSqlServer->GetPrefix());
pPrepStmt.reset(pSqlServer->Connection()->prepareStatement(aBuf));
pPrepStmt->setString(1, pSaveState);
pPrepStmt->setString(2, pData->m_Map);
pPrepStmt->setString(3, Code);
pPrepStmt->setString(4, pData->m_Server);
pPrepStmt->setString(5, aSaveID);
dbg_msg("sql", "%s", aBuf);
pPrepStmt->execute();
if(str_comp(pData->m_Server, g_Config.m_SvSqlServerName) == 0)
{
str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage),
"Team successfully saved by %s. Use '/load %s' to continue",
pData->m_ClientName, pData->m_Code.Str());
pData->m_pResult->m_Status = CSqlSaveResult::SAVE_SUCCESS;
pData->m_ClientName, Code);
}
else
{
dbg_msg("sql", "ERROR: This save-code already exists");
pData->m_pResult->m_Status = CSqlSaveResult::SAVE_FAILED;
strcpy(pData->m_pResult->m_aMessage, "This save-code already exists");
str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage),
"Team successfully saved by %s. Use '/load %s' on %s to continue",
pData->m_ClientName, Code, pData->m_Server);
}
pData->m_pResult->m_Status = CSqlSaveResult::SAVE_SUCCESS;
}
catch (sql::SQLException &e)
else
{
dbg_msg("sql", "ERROR: This save-code already exists");
pData->m_pResult->m_Status = CSqlSaveResult::SAVE_FAILED;
dbg_msg("sql", "MySQL Error: %s", e.what());
dbg_msg("sql", "ERROR: Could not save the team");
strcpy(pData->m_pResult->m_aMessage, "MySQL Error: Could not save the team");
pSqlServer->executeSql("unlock tables;");
return false;
strcpy(pData->m_pResult->m_aMessage, "This save-code already exists");
}
}
catch (sql::SQLException &e)
{
pData->m_pResult->m_Status = CSqlSaveResult::SAVE_FAILED;
dbg_msg("sql", "MySQL Error: %s", e.what());
dbg_msg("sql", "ERROR: Could not save the team");
strcpy(pData->m_pResult->m_aMessage, "MySQL Error: Could not save the team");
pSqlServer->executeSql("unlock tables;");
return false;
}
pSqlServer->executeSql("unlock tables;");
return true;

View file

@ -5,6 +5,7 @@
#define GAME_SERVER_SCORE_SQL_H
#include <engine/server/sql_string_helpers.h>
#include <game/prng.h>
#include "../score.h"
@ -40,7 +41,7 @@ struct CSqlPlayerResult
struct CSqlSaveResult {
CSqlSaveResult(int PlayerID, IGameController* Controller) :
m_Status(SAVE_SUCCESS),
m_Status(SAVE_FAILED),
m_SavedTeam(CSaveTeam(Controller)),
m_RequestingPlayer(PlayerID)
{
@ -137,8 +138,9 @@ struct CSqlTeamSave : CSqlData<CSqlSaveResult>
char m_ClientName[MAX_NAME_LENGTH];
sqlstr::CSqlString<128> m_Map;
sqlstr::CSqlString<128> m_Code;
char m_Map[128];
char m_Code[128];
char m_aGeneratedCode[128];
char m_Server[5];
};
@ -188,7 +190,6 @@ class CSqlScore: public IScore
{
static LOCK ms_FailureFileLock;
static bool Init(CSqlServer* pSqlServer, const CSqlData<CSqlInitResult> *pGameData, bool HandleFailure);
static bool RandomMapThread(CSqlServer* pSqlServer, const CSqlData<CSqlPlayerResult> *pGameData, bool HandleFailure = false);
@ -218,6 +219,10 @@ class CSqlScore: public IScore
CGameContext *m_pGameServer;
IServer *m_pServer;
std::vector<std::string> m_aWordlist;
CPrng m_Prng;
void GeneratePassphrase(char *pBuf, int BufSize);
char m_aMap[64];
char m_aGameUuid[UUID_MAXSTRSIZE];