+ // Make sure it fits in the buffer
+ if (length < 0)
+ goto bad;
+
+ if (fullstatus)
+ {
+ char *ptr;
+ int left;
+ int savelength;
+
+ savelength = length;
+
+ ptr = out_msg + length;
+ left = (int)out_size - length;
+
+ for (i = 0;i < (unsigned int)svs.maxclients;i++)
+ {
+ client_t *client = &svs.clients[i];
+ if (client->active)
+ {
+ int nameind, cleanind, pingvalue;
+ char curchar;
+ char cleanname [sizeof(client->name)];
+ const char *statusstr;
+ prvm_edict_t *ed;
+
+ // Remove all characters '"' and '\' in the player name
+ nameind = 0;
+ cleanind = 0;
+ do
+ {
+ curchar = client->name[nameind++];
+ if (curchar != '"' && curchar != '\\')
+ {
+ cleanname[cleanind++] = curchar;
+ if (cleanind == sizeof(cleanname) - 1)
+ break;
+ }
+ } while (curchar != '\0');
+ cleanname[cleanind] = 0; // cleanind is always a valid index even at this point
+
+ pingvalue = (int)(client->ping * 1000.0f);
+ if(client->netconnection)
+ pingvalue = bound(1, pingvalue, 9999);
+ else
+ pingvalue = 0;
+
+ *qcstatus = 0;
+ ed = PRVM_EDICT_NUM(i + 1);
+ statusstr = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
+ if(statusstr && *statusstr)
+ {
+ char *p;
+ const char *q;
+ p = qcstatus;
+ for(q = statusstr; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
+ if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
+ *p++ = *q;
+ *p = 0;
+ }
+
+ if (IS_NEXUIZ_DERIVED(gamemode) && (teamplay.integer > 0))
+ {
+ if(client->frags == -666) // spectator
+ strlcpy(teambuf, " 0", sizeof(teambuf));
+ else if(client->colors == 0x44) // red team
+ strlcpy(teambuf, " 1", sizeof(teambuf));
+ else if(client->colors == 0xDD) // blue team
+ strlcpy(teambuf, " 2", sizeof(teambuf));
+ else if(client->colors == 0xCC) // yellow team
+ strlcpy(teambuf, " 3", sizeof(teambuf));
+ else if(client->colors == 0x99) // pink team
+ strlcpy(teambuf, " 4", sizeof(teambuf));
+ else
+ strlcpy(teambuf, " 0", sizeof(teambuf));
+ }
+ else
+ *teambuf = 0;
+
+ // note: team number is inserted according to SoF2 protocol
+ if(*qcstatus)
+ length = dpsnprintf(ptr, left, "%s %d%s \"%s\"\n",
+ qcstatus,
+ pingvalue,
+ teambuf,
+ cleanname);
+ else
+ length = dpsnprintf(ptr, left, "%d %d%s \"%s\"\n",
+ client->frags,
+ pingvalue,
+ teambuf,
+ cleanname);
+
+ if(length < 0)
+ {
+ // out of space?
+ // turn it into an infoResponse!
+ out_msg[savelength] = 0;
+ memcpy(out_msg + 4, "infoResponse\x0A", 13);
+ memmove(out_msg + 17, out_msg + 19, savelength - 19);
+ break;
+ }
+ left -= length;
+ ptr += length;
+ }
+ }
+ }
+
+ return true;
+
+bad:
+ return false;
+}
+
+static qbool NetConn_PreventFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength, double floodtime, qbool renew)
+{
+ size_t floodslotnum, bestfloodslotnum;
+ double bestfloodtime;
+ lhnetaddress_t noportpeeraddress;
+ // see if this is a connect flood
+ noportpeeraddress = *peeraddress;
+ LHNETADDRESS_SetPort(&noportpeeraddress, 0);
+ bestfloodslotnum = 0;
+ bestfloodtime = floodlist[bestfloodslotnum].lasttime;
+ for (floodslotnum = 0;floodslotnum < floodlength;floodslotnum++)
+ {
+ if (bestfloodtime >= floodlist[floodslotnum].lasttime)
+ {
+ bestfloodtime = floodlist[floodslotnum].lasttime;
+ bestfloodslotnum = floodslotnum;
+ }
+ if (floodlist[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &floodlist[floodslotnum].address) == 0)
+ {
+ // this address matches an ongoing flood address
+ if (host.realtime < floodlist[floodslotnum].lasttime + floodtime)
+ {
+ if(renew)
+ {
+ // renew the ban on this address so it does not expire
+ // until the flood has subsided
+ floodlist[floodslotnum].lasttime = host.realtime;
+ }
+ //Con_Printf("Flood detected!\n");
+ return true;
+ }
+ // the flood appears to have subsided, so allow this
+ bestfloodslotnum = floodslotnum; // reuse the same slot
+ break;
+ }
+ }
+ // begin a new timeout on this address
+ floodlist[bestfloodslotnum].address = noportpeeraddress;
+ floodlist[bestfloodslotnum].lasttime = host.realtime;
+ //Con_Printf("Flood detection initiated!\n");
+ return false;
+}
+
+void NetConn_ClearFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength)
+{
+ size_t floodslotnum;
+ lhnetaddress_t noportpeeraddress;
+ // see if this is a connect flood
+ noportpeeraddress = *peeraddress;
+ LHNETADDRESS_SetPort(&noportpeeraddress, 0);
+ for (floodslotnum = 0;floodslotnum < floodlength;floodslotnum++)
+ {
+ if (floodlist[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &floodlist[floodslotnum].address) == 0)
+ {
+ // this address matches an ongoing flood address
+ // remove the ban
+ floodlist[floodslotnum].address.addresstype = LHNETADDRESSTYPE_NONE;
+ floodlist[floodslotnum].lasttime = 0;
+ //Con_Printf("Flood cleared!\n");
+ }
+ }
+}
+
+typedef qbool (*rcon_matchfunc_t) (lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen);
+
+static qbool hmac_mdfour_time_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
+{
+ char mdfourbuf[16];
+ long t1, t2;
+
+ if (!password[0]) {
+ Con_Print(CON_ERROR "LOGIC ERROR: RCon_Authenticate should never call the comparator with an empty password. Please report.\n");
+ return false;
+ }
+
+ t1 = (long) time(NULL);
+ t2 = strtol(s, NULL, 0);
+ if(labs(t1 - t2) > rcon_secure_maxdiff.integer)
+ return false;
+
+ if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, (int)strlen(password)))
+ return false;
+
+ return !memcmp(mdfourbuf, hash, 16);
+}
+
+static qbool hmac_mdfour_challenge_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
+{
+ char mdfourbuf[16];
+ int i;
+
+ if (!password[0]) {
+ Con_Print(CON_ERROR "LOGIC ERROR: RCon_Authenticate should never call the comparator with an empty password. Please report.\n");
+ return false;
+ }
+
+ if(slen < (int)(sizeof(challenges[0].string)) - 1)
+ return false;
+
+ // validate the challenge
+ for (i = 0;i < MAX_CHALLENGES;i++)
+ if(challenges[i].time > 0)
+ if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address) && !strncmp(challenges[i].string, s, sizeof(challenges[0].string) - 1))
+ break;
+ // if the challenge is not recognized, drop the packet
+ if (i == MAX_CHALLENGES)
+ return false;
+
+ if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, (int)strlen(password)))
+ return false;
+
+ if(memcmp(mdfourbuf, hash, 16))
+ return false;
+
+ // unmark challenge to prevent replay attacks
+ challenges[i].time = 0;
+
+ return true;
+}
+
+static qbool plaintext_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
+{
+ if (!password[0]) {
+ Con_Print(CON_ERROR "LOGIC ERROR: RCon_Authenticate should never call the comparator with an empty password. Please report.\n");
+ return false;
+ }
+
+ return !strcmp(password, hash);
+}
+
+/// returns a string describing the user level, or NULL for auth failure
+static const char *RCon_Authenticate(lhnetaddress_t *peeraddress, const char *password, const char *s, const char *endpos, rcon_matchfunc_t comparator, const char *cs, int cslen)
+{
+ const char *text, *userpass_start, *userpass_end, *userpass_startpass;
+ static char buf[MAX_INPUTLINE];
+ qbool hasquotes;
+ qbool restricted = false;
+ qbool have_usernames = false;
+ static char vabuf[1024];
+
+ userpass_start = rcon_password.string;
+ while((userpass_end = strchr(userpass_start, ' ')))
+ {
+ have_usernames = true;
+ strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1));
+ if(buf[0]) // Ignore empty entries due to leading/duplicate space.
+ if(comparator(peeraddress, buf, password, cs, cslen))
+ goto allow;
+ userpass_start = userpass_end + 1;
+ }
+ if(userpass_start[0]) // Ignore empty trailing entry due to trailing space or password not set.
+ {
+ userpass_end = userpass_start + strlen(userpass_start);
+ if(comparator(peeraddress, userpass_start, password, cs, cslen))
+ goto allow;
+ }