diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 8e2a42de..e1c4f8bc 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1126,13 +1126,55 @@ typedef enum CL_DOWNLOADSAVEGAME, #endif CL_CONNECTED, - CL_ABORTED + CL_ABORTED, + CL_CHALLENGE } cl_mode_t; static void GetPackets(void); static cl_mode_t cl_mode = CL_SEARCHING; +static UINT8 cl_challengenum = 0; +static UINT8 cl_challengequestion[17]; +static char cl_challengepassword[65]; +static UINT8 cl_challengeanswer[17]; + +static void D_JoinChallengeInput(INT32 ch) +{ + size_t len; + + while (ch) + { + if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && hu_font[ch-HU_FONTSTART]) + || ch == ' ') // Allow spaces, of course + { + len = strlen(cl_challengepassword); + if (len < 64) + { + cl_challengepassword[len+1] = 0; + cl_challengepassword[len] = ch; + } + } + else if (ch == KEY_BACKSPACE) + { + len = strlen(cl_challengepassword); + + if (len > 0) + cl_challengepassword[len-1] = 0; + } + else if (ch == KEY_ENTER) + { + // Done? + D_ComputeChallengeAnswer(cl_challengequestion, cl_challengepassword, cl_challengeanswer); + cl_mode = CL_ASKJOIN; + return; + } + + ch = I_GetKey(); + } + +} + // Player name send/load static void CV_SavePlayerNames(UINT8 **p) @@ -1191,11 +1233,25 @@ static inline void CL_DrawConnectionStatus(void) // 15 pal entries total. const char *cltext; - for (i = 0; i < 16; ++i) - V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-24, 16, 8, palstart + ((animtime - i) & 15)); + if (cl_mode != CL_CHALLENGE) + for (i = 0; i < 16; ++i) + V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-24, 16, 8, palstart + ((animtime - i) & 15)); switch (cl_mode) { + case CL_CHALLENGE: + { + char asterisks[65]; + size_t sl = strlen(cl_challengepassword); + + memset(asterisks, '*', sl); + memset(asterisks+sl, 0, 65-sl); + + V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_MONOSPACE|V_ALLOWLOWERCASE, asterisks); + + cltext = M_GetText("Please enter the server password."); + } + break; #ifdef JOININGAME case CL_DOWNLOADSAVEGAME: if (lastfilenum != -1) @@ -1292,6 +1348,8 @@ static boolean CL_SendJoin(void) netbuffer->u.clientcfg.localplayers = localplayers; netbuffer->u.clientcfg.version = VERSION; netbuffer->u.clientcfg.subversion = SUBVERSION; + netbuffer->u.clientcfg.challengenum = cl_challengenum; + memcpy(netbuffer->u.clientcfg.challengeanswer, cl_challengeanswer, 16); return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak)); } @@ -2059,6 +2117,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic break; #endif + case CL_CHALLENGE: case CL_WAITJOINRESPONSE: case CL_CONNECTED: default: @@ -2091,6 +2150,8 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic D_StartTitle(); return false; } + else if (cl_mode == CL_CHALLENGE) + D_JoinChallengeInput(key); // why are these here? this is for servers, we're a client //if (key == 's' && server) @@ -3142,6 +3203,9 @@ void D_ClientServerInit(void) gametic = 0; localgametic = 0; + memset(cl_challengequestion, 0x00, 17); + memset(cl_challengeanswer, 0x00, 17); + // do not send anything before the real begin SV_StopServer(); SV_ResetServer(); @@ -3631,6 +3695,26 @@ static void HandleConnect(SINT8 node) boolean newnode = false; #endif + if (D_IsJoinPasswordOn()) + { + // Ensure node sent the correct password challenge + boolean passed = false; + + if (netbuffer->u.clientcfg.challengenum && D_VerifyJoinPasswordChallenge(netbuffer->u.clientcfg.challengenum, netbuffer->u.clientcfg.challengeanswer)) + passed = true; + + if (!passed) + { + D_MakeJoinPasswordChallenge(&netbuffer->u.joinchallenge.challengenum, netbuffer->u.joinchallenge.question); + + netbuffer->packettype = PT_JOINCHALLENGE; + HSendPacket(node, true, 0, sizeof(joinchallenge_pak)); + //Net_CloseConnection(node); + + return; + } + } + // client authorised to join nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]); if (!nodeingame[node]) @@ -3794,6 +3878,22 @@ static void HandlePacketFromAwayNode(SINT8 node) Net_CloseConnection(node); break; + case PT_JOINCHALLENGE: + if (server && serverrunning) + { // But wait I thought I'm the server? + Net_CloseConnection(node); + break; + } + SERVERONLY + if (cl_mode == CL_WAITJOINRESPONSE) + { + cl_challengenum = netbuffer->u.joinchallenge.challengenum; + memcpy(cl_challengequestion, netbuffer->u.joinchallenge.question, 16); + + cl_mode = CL_CHALLENGE; + } + break; + case PT_SERVERREFUSE: // Negative response of client join request if (server && serverrunning) { // But wait I thought I'm the server? diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 6615d67d..e4ff7cc7 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -73,6 +73,8 @@ typedef enum PT_CLIENT4MIS, PT_BASICKEEPALIVE,// Keep the network alive during wipes, as tics aren't advanced and NetUpdate isn't called + PT_JOINCHALLENGE, // You must give a password to joinnnnn + PT_CANFAIL, // This is kind of a priority. Anything bigger than CANFAIL // allows HSendPacket(*, true, *, *) to return false. // In addition, this packet can't occupy all the available slots. @@ -354,8 +356,16 @@ typedef struct UINT8 subversion; // Contains build version UINT8 localplayers; UINT8 mode; + UINT8 challengenum; // Non-zero if trying to join with a password attempt + UINT8 challengeanswer[16]; // Join challenge } ATTRPACK clientconfig_pak; +typedef struct +{ + UINT8 challengenum; // Number to send back in join attempt + UINT8 question[16]; // Challenge data to be manipulated and answered with +} ATTRPACK joinchallenge_pak; + #define MAXSERVERNAME 32 #define MAXFILENEEDED 915 // This packet is too large @@ -447,7 +457,8 @@ typedef struct UINT8 resynchgot; // UINT8 textcmd[MAXTEXTCMD+1]; // 66049 bytes (wut??? 64k??? More like 257 bytes...) filetx_pak filetxpak; // 139 bytes - clientconfig_pak clientcfg; // 136 bytes + clientconfig_pak clientcfg; // 153 bytes + joinchallenge_pak joinchallenge; // 17 bytes serverinfo_pak serverinfo; // 1024 bytes serverrefuse_pak serverrefuse; // 65025 bytes (somehow I feel like those values are garbage...) askinfo_pak askinfo; // 61 bytes diff --git a/src/d_netcmd.c b/src/d_netcmd.c index a8efd306..a8014ec4 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -169,6 +169,7 @@ static void Got_Verification(UINT8 **cp, INT32 playernum); static void Got_Removal(UINT8 **cp, INT32 playernum); static void Command_Verify_f(void); static void Command_RemoveAdmin_f(void); +static void Command_ChangeJoinPassword_f(void); static void Command_MotD_f(void); static void Got_MotD_f(UINT8 **cp, INT32 playernum); @@ -534,6 +535,7 @@ void D_RegisterServerCommands(void) RegisterNetXCmd(XD_PICKVOTE, Got_PickVotecmd); // Remote Administration + COM_AddCommand("joinpassword", Command_ChangeJoinPassword_f); COM_AddCommand("password", Command_Changepassword_f); RegisterNetXCmd(XD_LOGIN, Got_Login); COM_AddCommand("login", Command_Login_f); // useful in dedicated to kick off remote admin @@ -3427,6 +3429,7 @@ static void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, if (len > 256-sl) len = 256-sl; + memcpy(tmpbuf, buffer, len); memmove(&tmpbuf[len], salt, sl); //strcpy(&tmpbuf[len], salt); @@ -3702,6 +3705,107 @@ static void Got_Removal(UINT8 **cp, INT32 playernum) CONS_Printf(M_GetText("You are no longer a server administrator.\n")); } +// Join password stuff +#define NUMJOINCHALLENGES 32 +static UINT8 joinpassmd5[17]; +static boolean joinpasswordset = false; +static UINT8 joinpasschallenges[NUMJOINCHALLENGES][17]; +static boolean joinpasschallengeson[NUMJOINCHALLENGES]; + +boolean D_IsJoinPasswordOn(void) +{ + return joinpasswordset; +} + +static inline void GetChallengeAnswer(UINT8 *question, UINT8 *passwordmd5, UINT8 *answer) +{ + D_MD5PasswordPass(question, 16, (char *) passwordmd5, answer); +} + +void D_ComputeChallengeAnswer(UINT8 *question, const char *pw, UINT8 *answer) +{ + static UINT8 passwordmd5[17]; + + memset(passwordmd5, 0x00, 17); + D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &passwordmd5); + GetChallengeAnswer(question, passwordmd5, answer); +} + +void D_SetJoinPassword(const char *pw) +{ + memset(joinpassmd5, 0x00, 17); + D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &joinpassmd5); + joinpasswordset = true; +} + +boolean D_VerifyJoinPasswordChallenge(UINT8 num, UINT8 *answer) +{ + boolean passed = false; + + num %= NUMJOINCHALLENGES; + + //@TODO use a constant-time memcmp.... + if (joinpasschallengeson[num] && memcmp(answer, joinpasschallenges[num], 16) == 0) + passed = true; + + // Wipe and reset the challenge so that it can't be tried against again, as a small measure against brute-force attacks. + memset(joinpasschallenges[num], 0x00, 17); + joinpasschallengeson[num] = false; + + return passed; +} + +void D_MakeJoinPasswordChallenge(UINT8 *num, UINT8 *question) +{ + size_t i; + + for (i = 0; i < NUMJOINCHALLENGES; i++) + { + (*num) = M_RandomKey(NUMJOINCHALLENGES); + + if (!joinpasschallengeson[(*num)]) + break; + } + + // If the above loop never breaks, then uh.... we're obliterating one random stored challenge. Sorry (: + joinpasschallengeson[(*num)] = true; + + memset(question, 0x00, 17); + for (i = 0; i < 16; i++) + question[i] = M_RandomByte(); + + // Store the answer in memory. What was the question again? + GetChallengeAnswer(question, joinpassmd5, joinpasschallenges[(*num)]); + + // This ensures that num is always non-zero and will be valid when used for the answer + if ((*num) == 0) + (*num) = NUMJOINCHALLENGES; +} + +// Remote Administration +static void Command_ChangeJoinPassword_f(void) +{ +#ifdef NOMD5 + // If we have no MD5 support then completely disable XD_LOGIN responses for security. + CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n"); +#else + if (client) // cannot change remotely + { + CONS_Printf(M_GetText("Only the server can use this.\n")); + return; + } + + if (COM_Argc() != 2) + { + CONS_Printf(M_GetText("joinpassword : set a password to join the server\n")); + return; + } + + D_SetJoinPassword(COM_Argv(1)); + CONS_Printf(M_GetText("Join password set.\n")); +#endif +} + static void Command_MotD_f(void) { size_t i, j; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index e0827013..438ba3ff 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -248,6 +248,12 @@ void RemoveAdminPlayer(INT32 playernum); void ItemFinder_OnChange(void); void D_SetPassword(const char *pw); +boolean D_IsJoinPasswordOn(void); +void D_ComputeChallengeAnswer(UINT8 *question, const char *pw, UINT8 *answer); +void D_SetJoinPassword(const char *pw); +boolean D_VerifyJoinPasswordChallenge(UINT8 num, UINT8 *answer); +void D_MakeJoinPasswordChallenge(UINT8 *num, UINT8 *question); + // used for the player setup menu UINT8 CanChangeSkin(INT32 playernum);