diff --git a/src/base/system.c b/src/base/system.c index c267130bd..1a7e57f90 100644 --- a/src/base/system.c +++ b/src/base/system.c @@ -2555,7 +2555,40 @@ int secure_random_init() #endif } -void secure_random_fill(void *bytes, size_t length) +void generate_password(char *buffer, unsigned length, unsigned short *random, unsigned random_length) +{ + static const char VALUES[] = "ABCDEFGHKLMNPRSTUVWXYZabcdefghjkmnopqt23456789"; + static const size_t NUM_VALUES = sizeof(VALUES) - 1; // Disregard the '\0'. + unsigned i; + dbg_assert(length >= random_length * 2 + 1, "too small buffer"); + dbg_assert(NUM_VALUES * NUM_VALUES >= 2048, "need at least 2048 possibilities for 2-character sequences"); + + buffer[random_length * 2] = 0; + + for(i = 0; i < random_length; i++) + { + unsigned short random_number = random[i] % 2048; + buffer[2 * i + 0] = VALUES[random_number / NUM_VALUES]; + buffer[2 * i + 1] = VALUES[random_number % NUM_VALUES]; + } +} + +void secure_random_password(char *buffer, unsigned length, unsigned pw_length) +{ + static const size_t MAX_PASSWORD_LENGTH = 128; + unsigned short random[MAX_PASSWORD_LENGTH / 2]; + // With 6 characters, we get a password entropy of log(2048) * 6/2 = 33bit. + dbg_assert(length >= pw_length + 1, "too small buffer"); + dbg_assert(pw_length >= 6, "too small password length"); + dbg_assert(pw_length % 2 == 0, "need an even password length"); + dbg_assert(pw_length <= MAX_PASSWORD_LENGTH, "too large password length"); + + secure_random_fill(random, pw_length); + + generate_password(buffer, length, random, pw_length / 2); +} + +void secure_random_fill(void *bytes, unsigned length) { if(!secure_random_data.initialized) { diff --git a/src/base/system.h b/src/base/system.h index 5e3c0084a..e98191220 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -1375,6 +1375,20 @@ void shell_execute(const char *file); */ int os_compare_version(int major, int minor); +/* + Function: generate_password + Generates a null-terminated password of length `2 * + random_length`. + + + Parameters: + buffer - Pointer to the start of the output buffer. + length - Length of the buffer. + random - Pointer to a randomly-initialized array of shorts. + random_length - Length of the short array. +*/ +void generate_password(char *buffer, unsigned length, unsigned short *random, unsigned random_length); + /* Function: secure_random_init Initializes the secure random module. @@ -1386,6 +1400,21 @@ int os_compare_version(int major, int minor); */ int secure_random_init(); +/* + Function: secure_random_password + Fills the buffer with the specified amount of random password + characters. + + The desired password length must be greater or equal to 6, even + and smaller or equal to 128. + + Parameters: + buffer - Pointer to the start of the buffer. + length - Length of the buffer. + pw_length - Length of the desired password. +*/ +void secure_random_password(char *buffer, unsigned length, unsigned pw_length); + /* Function: secure_random_fill Fills the buffer with the specified amount of random bytes. @@ -1394,7 +1423,7 @@ int secure_random_init(); buffer - Pointer to the start of the buffer. length - Length of the buffer. */ -void secure_random_fill(void *bytes, size_t length); +void secure_random_fill(void *bytes, unsigned length); /* Function: secure_rand diff --git a/src/engine/client.h b/src/engine/client.h index 69bb39d06..4bdcef5c2 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -195,6 +195,8 @@ public: virtual void RequestDDNetSrvList() = 0; virtual bool EditorHasUnsavedData() = 0; + virtual void GenerateTimeoutSeed() = 0; + virtual IFriends* Foes() = 0; }; diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 33e85e259..1c98b07d6 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -32,6 +32,8 @@ #include #include +#include + #include #include #include @@ -347,6 +349,8 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta) m_DDNetSrvListTokenSet = false; m_ReconnectTime = 0; + + m_GenerateTimeoutSeed = true; } // ----- send functions ----- @@ -678,8 +682,51 @@ void CClient::EnterGame() OnEnterGame(); ServerInfoRequest(); // fresh one for timeout protection - m_TimeoutCodeSent[0] = false; - m_TimeoutCodeSent[1] = false; + m_aTimeoutCodeSent[0] = false; + m_aTimeoutCodeSent[1] = false; +} + +void GenerateTimeoutCode(char *pBuffer, unsigned Size, char *pSeed, const NETADDR &Addr, bool Dummy) +{ + md5_state_t Md5; + md5_byte_t aDigest[16]; + md5_init(&Md5); + + const char *pDummy = Dummy ? "dummy" : "normal"; + + md5_append(&Md5, (unsigned char *)pDummy, str_length(pDummy) + 1); + md5_append(&Md5, (unsigned char *)pSeed, str_length(pSeed) + 1); + md5_append(&Md5, (unsigned char *)&Addr, sizeof(Addr)); + md5_finish(&Md5, aDigest); + + unsigned short Random[8]; + mem_copy(Random, aDigest, sizeof(Random)); + generate_password(pBuffer, Size, Random, 8); +} + +void CClient::GenerateTimeoutSeed() +{ + secure_random_password(g_Config.m_ClTimeoutSeed, sizeof(g_Config.m_ClTimeoutSeed), 16); +} + +void CClient::GenerateTimeoutCodes() +{ + if(g_Config.m_ClTimeoutSeed[0]) + { + for(int i = 0; i < 2; i++) + { + GenerateTimeoutCode(m_aTimeoutCodes[i], sizeof(m_aTimeoutCodes[i]), g_Config.m_ClTimeoutSeed, m_ServerAddress, i); + + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "timeout code '%s' (%s)", m_aTimeoutCodes[i], i == 0 ? "normal" : "dummy"); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); + } + } + else + { + str_copy(m_aTimeoutCodes[0], g_Config.m_ClTimeoutCode, sizeof(m_aTimeoutCodes[0])); + str_copy(m_aTimeoutCodes[1], g_Config.m_ClDummyTimeoutCode, sizeof(m_aTimeoutCodes[1])); + } } void CClient::Connect(const char *pAddress) @@ -715,6 +762,8 @@ void CClient::Connect(const char *pAddress) m_InputtimeMarginGraph.Init(-150.0f, 150.0f); m_GametimeMarginGraph.Init(-150.0f, 150.0f); + + GenerateTimeoutCodes(); } void CClient::DisconnectWithReason(const char *pReason) @@ -1872,15 +1921,15 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) m_GameTime[g_Config.m_ClDummy].Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0); } - if(m_ReceivedSnapshots[g_Config.m_ClDummy] > 50 && !m_TimeoutCodeSent[g_Config.m_ClDummy]) + if(m_ReceivedSnapshots[g_Config.m_ClDummy] > 50 && !m_aTimeoutCodeSent[g_Config.m_ClDummy]) { if(IsDDNet(&m_CurrentServerInfo)) { - m_TimeoutCodeSent[g_Config.m_ClDummy] = true; + m_aTimeoutCodeSent[g_Config.m_ClDummy] = true; CNetMsg_Cl_Say Msg; Msg.m_Team = 0; char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "/timeout %s", g_Config.m_ClDummy ? g_Config.m_ClDummyTimeoutCode : g_Config.m_ClTimeoutCode); + str_format(aBuf, sizeof(aBuf), "/timeout %s", m_aTimeoutCodes[g_Config.m_ClDummy]); Msg.m_pMessage = aBuf; CMsgPacker Packer(Msg.MsgID()); Msg.Pack(&Packer); @@ -2577,7 +2626,14 @@ void CClient::Run() m_LocalStartTime = time_get(); m_SnapshotParts = 0; - srand(time(NULL)); + if(m_GenerateTimeoutSeed) + { + GenerateTimeoutSeed(); + } + + unsigned int Seed; + secure_random_fill(&Seed, sizeof(Seed)); + srand(Seed); // init SDL { @@ -3278,6 +3334,14 @@ void CClient::ConchainWindowVSync(IConsole::IResult *pResult, void *pUserData, I pfnCallback(pResult, pCallbackUserData); } +void CClient::ConchainTimeoutSeed(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments()) + pSelf->m_GenerateTimeoutSeed = false; +} + void CClient::RegisterCommands() { m_pConsole = Kernel()->RequestInterface(); @@ -3316,6 +3380,8 @@ void CClient::RegisterCommands() m_pConsole->Register("demo_speed", "i[speed]", CFGFLAG_CLIENT, Con_DemoSpeed, this, "Set demo speed"); // used for server browser update + m_pConsole->Chain("cl_timeout_seed", ConchainTimeoutSeed, this); + m_pConsole->Chain("br_filter_string", ConchainServerBrowserUpdate, this); m_pConsole->Chain("br_filter_gametype", ConchainServerBrowserUpdate, this); m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this); diff --git a/src/engine/client/client.h b/src/engine/client/client.h index b64943984..512d11d7a 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -119,7 +119,9 @@ class CClient : public IClient, public CDemoPlayer::IListener char m_aCurrentMapPath[CEditor::MAX_PATH_LENGTH]; unsigned m_CurrentMapCrc; - bool m_TimeoutCodeSent[2]; + char m_aTimeoutCodes[2][32]; + bool m_aTimeoutCodeSent[2]; + bool m_GenerateTimeoutSeed; // char m_aCmdConnect[256]; @@ -342,6 +344,7 @@ public: static void ConchainWindowBordered(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainWindowScreen(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainWindowVSync(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainTimeoutSeed(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void Con_DemoSlice(IConsole::IResult *pResult, void *pUserData); static void Con_DemoSliceBegin(IConsole::IResult *pResult, void *pUserData); @@ -371,6 +374,9 @@ public: // DDRace + void GenerateTimeoutSeed(); + void GenerateTimeoutCodes(); + virtual const char* GetCurrentMap(); virtual int GetCurrentMapCrc(); virtual const char* GetCurrentMapPath(); diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 21db185b4..efa90fe1b 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -559,26 +559,7 @@ void CServer::InitRconPasswordIfEmpty() return; } - static const char VALUES[] = "ABCDEFGHKLMNPRSTUVWXYZabcdefghjkmnopqt23456789"; - static const size_t NUM_VALUES = sizeof(VALUES) - 1; // Disregard the '\0'. - static const size_t PASSWORD_LENGTH = 6; - dbg_assert(NUM_VALUES * NUM_VALUES >= 2048, "need at least 2048 possibilities for 2-character sequences"); - // With 6 characters, we get a password entropy of log(2048) * 6/2 = 33bit. - - dbg_assert(PASSWORD_LENGTH % 2 == 0, "need an even password length"); - unsigned short aRandom[PASSWORD_LENGTH / 2]; - char aRandomPassword[PASSWORD_LENGTH+1]; - aRandomPassword[PASSWORD_LENGTH] = 0; - - secure_random_fill(aRandom, sizeof(aRandom)); - for(size_t i = 0; i < PASSWORD_LENGTH / 2; i++) - { - unsigned short RandomNumber = aRandom[i] % 2048; - aRandomPassword[2 * i + 0] = VALUES[RandomNumber / NUM_VALUES]; - aRandomPassword[2 * i + 1] = VALUES[RandomNumber % NUM_VALUES]; - } - - str_copy(g_Config.m_SvRconPassword, aRandomPassword, sizeof(g_Config.m_SvRconPassword)); + secure_random_password(g_Config.m_SvRconPassword, sizeof(g_Config.m_SvRconPassword), 6); m_GeneratedRconPassword = 1; } diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 91bd0117f..e524624f1 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -363,6 +363,7 @@ MACRO_CONFIG_INT(ClOldGunPosition, cl_old_gun_position, 0, 0, 1, CFGFLAG_SAVE|CF MACRO_CONFIG_INT(ClConfirmDisconnect, cl_confirm_disconnect, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Confirmation popup before disconnecting") MACRO_CONFIG_STR(ClTimeoutCode, cl_timeout_code, 64, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Timeout code to use") MACRO_CONFIG_STR(ClDummyTimeoutCode, cl_dummy_timeout_code, 64, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Dummy Timeout code to use") +MACRO_CONFIG_STR(ClTimeoutSeed, cl_timeout_seed, 64, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Timeout seed") MACRO_CONFIG_STR(ClInputFifo, cl_input_fifo, 128, "", CFGFLAG_SAVE|CFGFLAG_CLIENT, "Fifo file to use as input for client console") MACRO_CONFIG_INT(ClShowConsole, cl_show_console, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Show console window (Windows only)") #if defined(__ANDROID__) diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 432839073..8f1a5f0a6 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2020,24 +2020,11 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) #endif { + static int s_ButtonTimeout = 0; Right.HSplitTop(20.0f, &Button, &Right); - Button.VSplitLeft(190.0f, &Label, &Button); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%s:", Localize("Timeout code")); - UI()->DoLabelScaled(&Label, aBuf, 14.0, -1); - static float s_OffsetCode = 0.0f; - DoEditBox(g_Config.m_ClTimeoutCode, &Button, g_Config.m_ClTimeoutCode, sizeof(g_Config.m_ClTimeoutCode), 14.0f, &s_OffsetCode); - } - - Right.HSplitTop(5.0f, &Button, &Right); - - { - Right.HSplitTop(20.0f, &Button, &Right); - Button.VSplitLeft(190.0f, &Label, &Button); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%s:", Localize("Dummy Timeout code")); - UI()->DoLabelScaled(&Label, aBuf, 14.0, -1); - static float s_OffsetCode = 0.0f; - DoEditBox(g_Config.m_ClDummyTimeoutCode, &Button, g_Config.m_ClDummyTimeoutCode, sizeof(g_Config.m_ClDummyTimeoutCode), 14.0f, &s_OffsetCode); + if(DoButton_Menu(&s_ButtonTimeout, Localize("New random timeout code"), 0, &Button)) + { + Client()->GenerateTimeoutSeed(); + } } }