diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 74e3fb327..83ab2486d 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -46,6 +46,7 @@ int startuppending; void Host_FinishLoading(void); +cvar_t cl_crypt_rcon = CVARFD("cl_crypt_rcon", "1", CVAR_ARCHIVE, "Controls whether to send a hash instead of sending rcon passwords as plain-text. Set to 1 for security, or 0 for backwards compatibility.\nYour command and any responses will still be sent as plain text.\nInstead, it is recommended to use rcon ONLY via dtls/tls/wss connections."); cvar_t rcon_password = CVARF("rcon_password", "", CVAR_NOUNSAFEEXPAND); cvar_t rcon_address = CVARF("rcon_address", "", CVAR_NOUNSAFEEXPAND); @@ -147,6 +148,9 @@ cvar_t noaim = CVARF("noaim", "", CVAR_ARCHIVE | CVAR_USERINFO); cvar_t msg = CVARFD("msg", "1", CVAR_ARCHIVE | CVAR_USERINFO, "Filter console prints/messages. Only functions on QuakeWorld servers. 0=pickup messages. 1=death messages. 2=critical messages. 3=chat."); cvar_t b_switch = CVARF("b_switch", "", CVAR_ARCHIVE | CVAR_USERINFO); cvar_t w_switch = CVARF("w_switch", "", CVAR_ARCHIVE | CVAR_USERINFO); +#ifdef HEXEN2 +cvar_t cl_playerclass=CVARF("cl_playerclass","", CVAR_ARCHIVE | CVAR_USERINFO); +#endif cvar_t cl_nofake = CVARD("cl_nofake", "2", "value 0: permits \\r chars in chat messages\nvalue 1: blocks all \\r chars\nvalue 2: allows \\r chars, but only from teammates"); cvar_t cl_chatsound = CVAR("cl_chatsound","1"); cvar_t cl_enemychatsound = CVAR("cl_enemychatsound", "misc/talk.wav"); @@ -1437,7 +1441,49 @@ void CL_Rcon_f (void) message[4] = 0; Q_strncatz (message, "rcon ", sizeof(message)); - Q_strncatz (message, password, sizeof(message)); + if (cl_crypt_rcon.ival) + { + char cryptpass[1024], crypttime[64]; + const char *hex = "0123456789ABCDEF"; //must be upper-case for compat with mvdsv. + time_t clienttime; + time(&clienttime); + size_t digestsize; + unsigned char digest[64]; + const unsigned char **tokens = alloca(sizeof(*tokens)*(4+Cmd_Argc()*2)); + size_t *tokensizes = alloca(sizeof(*tokensizes)*(4+Cmd_Argc()*2)); + int j, k; + + for (j = 0; j < sizeof(time_t); j++) + { //little-endian byte order, but big-endian nibble order. just screwed. for compat with ezquake. + crypttime[j*2+0] = hex[(clienttime>>(j*8+4))&0xf]; + crypttime[j*2+1] = hex[(clienttime>>(j*8))&0xf]; + } + crypttime[j*2] = 0; + + tokens[0] = "rcon "; + tokens[1] = password; + tokens[2] = crypttime; + tokens[3] = " "; + for (j=0 ; j>4]; + cryptpass[j*2+1] = hex[digest[j]&0xf]; + } + cryptpass[j*2] = 0; + Q_strncatz (message, cryptpass, sizeof(message)); + Q_strncatz (message, crypttime, sizeof(message)); + } + else + Q_strncatz (message, password, sizeof(message)); Q_strncatz (message, " ", sizeof(message)); for ( ; i= '0' && i <= '9') + return (i-'0'); + else if (i >= 'A' && i <= 'F') + return (i-'A'+10); + else + return (i-'a'+10); +} int Rcon_Validate (void) { - if (!strlen (rcon_password.string)) + const char *realpass = rcon_password.string; + const char *pass = Cmd_Argv(1); + if (!strlen (realpass)) return 0; - if (strcmp (Cmd_Argv(1), rcon_password.string) ) - return 0; + if (!sv_crypt_rcon.ival) + { //vanilla-compatible + if (!strcmp (pass, realpass) ) + return 1; + } + if (sv_crypt_rcon.ival || !*sv_crypt_rcon.string) + { //ezquake-compatible + //the password arg is "[SHA1Digest][unixtime-in-hex]" where the digest is "[arg0] password[unixtime] arg0 arg1 argn " + time_t clienttime = 0; + time_t servertime = 0; + intptr_t timediff; + qbyte b; - return 1; + const size_t digestsize = 20; + size_t i, k; + unsigned char digest[digestsize]; + const unsigned char **tokens = alloca(sizeof(*tokens)*(Cmd_Argc()*2+5)); //overallocation in case argc is 0. + size_t *toksizes = alloca(sizeof(*toksizes)*(Cmd_Argc()*2+5)); //overallocation in case argc is 0. + if (strlen(pass) > digestsize*2) + { + for (i = 0; pass[digestsize*2+i] && i < sizeof(time_t)*2; i++) + { //mixed endian for compat with ezquake + if (!(i & 1)) + clienttime |= dehex(pass[digestsize*2+i]) << (((i/2)*8)+4); + else + clienttime |= dehex(pass[digestsize*2+i]) << (((i/2)*8)+0); + } + time(&servertime); + timediff = servertime-clienttime; + //make sure the client's time is within our allowed bounds, to prevent (extreme) replay attacks. + if (!sv_crypt_rcon_clockskew.value || (timediff >= -sv_crypt_rcon_clockskew.ival && timediff <= sv_crypt_rcon_clockskew.ival)) + { + tokens[0] = Cmd_Argv(0); + tokens[1] = " "; + tokens[2] = realpass; + tokens[3] = pass+digestsize*2; + tokens[4] = " "; + for (i = 0; i < Cmd_Argc()-2; i++) + { + tokens[5+i*2+0] = Cmd_Argv(i+2); + tokens[5+i*2+1] = " "; //a trailing space is required. + } + for (k = 0; k < 5+i*2; k++) + toksizes[k] = strlen(tokens[k]); + if (digestsize > 0 && digestsize == SHA1_m(digest, sizeof(digest), k, tokens, toksizes)) + { + for (i = 0;;i++) + { + if (i == digestsize) + return 1; + if (!pass[i*2+0] || !pass[i*2+1]) + break; //premature termination + b = dehex(pass[i*2+0])*16+dehex(pass[i*2+1]); + if (b != digest[i]) + break; + } + } + } + } + } + return 0; } /* @@ -5077,6 +5146,7 @@ void SV_InitLocal (void) Log_Init(); } rcon_password.restriction = RESTRICT_MAX; //no cheatie rconers changing rcon passwords... + Cvar_Register (&sv_crypt_rcon, cvargroup_servercontrol); Cvar_Register (&spectator_password, cvargroup_servercontrol); Cvar_Register (&sv_mintic, cvargroup_servercontrol);