cvar_t net_connectfloodblockingtimeout = {0, "net_connectfloodblockingtimeout", "5", "when a connection packet is received, it will block all future connect packets from that IP address for this many seconds (cuts down on connect floods). Note that this does not include retries from the same IP; these are handled earlier and let in."};
cvar_t net_challengefloodblockingtimeout = {0, "net_challengefloodblockingtimeout", "0.5", "when a challenge packet is received, it will block all future challenge packets from that IP address for this many seconds (cuts down on challenge floods). DarkPlaces clients retry once per second, so this should be <= 1. Failure here may lead to connect attempts failing."};
cvar_t net_getstatusfloodblockingtimeout = {0, "net_getstatusfloodblockingtimeout", "1", "when a getstatus packet is received, it will block all future getstatus packets from that IP address for this many seconds (cuts down on getstatus floods). DarkPlaces retries every 4 seconds, and qstat retries once per second, so this should be <= 1. Failure here may lead to server not showing up in the server list."};
+cvar_t net_sourceaddresscheck = {0, "net_sourceaddresscheck", "1", "compare the source IP address for replies (more secure, may break some bad multihoming setups"};
cvar_t hostname = {CVAR_SAVE, "hostname", "UNNAMED", "server message to show in server browser"};
cvar_t developer_networking = {0, "developer_networking", "0", "prints all received and sent packets (recommended only for debugging)"};
int serverquerycount = 0;
int serverreplycount = 0;
-challenge_t challenge[MAX_CHALLENGES];
+challenge_t challenges[MAX_CHALLENGES];
#ifdef CONFIG_MENU
/// this is only false if there are still servers left to query
char ipstring[32];
const char *s;
#endif
- char vabuf[1024];
// quakeworld ingame packet
fromserver = cls.netcon && mysocket == cls.netcon->mysocket && !LHNETADDRESS_Compare(&cls.netcon->peeraddress, peeraddress);
{
// darkplaces or quake3
char protocolnames[1400];
- Protocol_Names(protocolnames, sizeof(protocolnames));
Con_DPrintf("\"%s\" received, sending connect request back to %s\n", string, addressstring2);
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+ Con_DPrintf("challenge message from wrong server %s\n", addressstring2);
+ return true;
+ }
+ Protocol_Names(protocolnames, sizeof(protocolnames));
#ifdef CONFIG_MENU
M_Update_Return_Reason("Got challenge response");
#endif
// update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command)
InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2);
// TODO: add userinfo stuff here instead of using NQ commands?
- NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377connect\\protocol\\darkplaces 3\\protocols\\%s%s\\challenge\\%s", protocolnames, cls.connect_userinfo, string + 10), peeraddress);
+ memcpy(senddata, "\377\377\377\377", 4);
+ dpsnprintf(senddata+4, sizeof(senddata)-4, "connect\\protocol\\darkplaces 3\\protocols\\%s%s\\challenge\\%s", protocolnames, cls.connect_userinfo, string + 10);
+ NetConn_WriteString(mysocket, senddata, peeraddress);
return true;
}
if (length == 6 && !memcmp(string, "accept", 6) && cls.connect_trying)
{
// darkplaces or quake3
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+ Con_DPrintf("accept message from wrong server %s\n", addressstring2);
+ return true;
+ }
#ifdef CONFIG_MENU
M_Update_Return_Reason("Accepted");
#endif
if (length > 7 && !memcmp(string, "reject ", 7) && cls.connect_trying)
{
char rejectreason[128];
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+ Con_DPrintf("reject message from wrong server %s\n", addressstring2);
+ return true;
+ }
cls.connect_trying = false;
string += 7;
length = min(length - 7, (int)sizeof(rejectreason) - 1);
if (length > 1 && string[0] == 'c' && (string[1] == '-' || (string[1] >= '0' && string[1] <= '9')) && cls.connect_trying)
{
// challenge message
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+ Con_DPrintf("c message from wrong server %s\n", addressstring2);
+ return true;
+ }
Con_Printf("challenge %s received, sending QuakeWorld connect request back to %s\n", string + 1, addressstring2);
#ifdef CONFIG_MENU
M_Update_Return_Reason("Got QuakeWorld challenge response");
cls.qw_qport = qport.integer;
// update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command)
InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2);
- NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377connect %i %i %i \"%s%s\"\n", 28, cls.qw_qport, atoi(string + 1), cls.userinfo, cls.connect_userinfo), peeraddress);
+ memcpy(senddata, "\377\377\377\377", 4);
+ dpsnprintf(senddata+4, sizeof(senddata)-4, "connect %i %i %i \"%s%s\"\n", 28, cls.qw_qport, atoi(string + 1), cls.userinfo, cls.connect_userinfo);
+ NetConn_WriteString(mysocket, senddata, peeraddress);
return true;
}
if (length >= 1 && string[0] == 'j' && cls.connect_trying)
{
// accept message
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+ Con_DPrintf("j message from wrong server %s\n", addressstring2);
+ return true;
+ }
#ifdef CONFIG_MENU
M_Update_Return_Reason("QuakeWorld Accepted");
#endif
}
if (string[0] == 'n')
{
- // qw print command
+ // qw print command, used by rcon replies too
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address) && LHNETADDRESS_Compare(peeraddress, &cls.rcon_address)) {
+ Con_DPrintf("n message from wrong server %s\n", addressstring2);
+ return true;
+ }
Con_Printf("QW print command from server at %s:\n%s\n", addressstring2, string + 1);
}
// we may not have liked the packet, but it was a command packet, so
if (cls.connect_trying)
{
lhnetaddress_t clientportaddress;
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) {
+ Con_DPrintf("CCREP_ACCEPT message from wrong server %s\n", addressstring2);
+ break;
+ }
clientportaddress = *peeraddress;
LHNETADDRESS_SetPort(&clientportaddress, MSG_ReadLong(&cl_message));
// extra ProQuake stuff
}
break;
case CCREP_REJECT:
- if (developer_extra.integer)
- Con_DPrintf("Datagram_ParseConnectionless: received CCREP_REJECT from %s.\n", addressstring2);
+ if (developer_extra.integer) {
+ Con_DPrintf("CCREP_REJECT message from wrong server %s\n", addressstring2);
+ break;
+ }
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address))
+ break;
cls.connect_trying = false;
#ifdef CONFIG_MENU
M_Update_Return_Reason((char *)MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)));
#endif
break;
case CCREP_RCON: // RocketGuy: ProQuake rcon support
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.rcon_address)) {
+ Con_DPrintf("CCREP_RCON message from wrong server %s\n", addressstring2);
+ break;
+ }
if (developer_extra.integer)
Con_DPrintf("Datagram_ParseConnectionless: received CCREP_RCON from %s.\n", addressstring2);
int length;
char teambuf[3];
const char *crypto_idstring;
- const char *str;
+ const char *worldstatusstr;
// How many clients are there?
for (i = 0;i < (unsigned int)svs.maxclients;i++)
}
*qcstatus = 0;
- str = PRVM_GetString(prog, PRVM_serverglobalstring(worldstatus));
- if(str && *str)
+ worldstatusstr = PRVM_GetString(prog, PRVM_serverglobalstring(worldstatus));
+ if(worldstatusstr && *worldstatusstr)
{
char *p;
const char *q;
p = qcstatus;
- for(q = str; *q && (size_t)(p - qcstatus) < (sizeof(qcstatus) - 1); ++q)
+ for(q = worldstatusstr; *q && (size_t)(p - qcstatus) < (sizeof(qcstatus) - 1); ++q)
if(*q != '\\' && *q != '\n')
*p++ = *q;
*p = 0;
for (i = 0;i < (unsigned int)svs.maxclients;i++)
{
- client_t *cl = &svs.clients[i];
- if (cl->active)
+ client_t *client = &svs.clients[i];
+ if (client->active)
{
int nameind, cleanind, pingvalue;
char curchar;
- char cleanname [sizeof(cl->name)];
- const char *str;
+ char cleanname [sizeof(client->name)];
+ const char *statusstr;
prvm_edict_t *ed;
// Remove all characters '"' and '\' in the player name
cleanind = 0;
do
{
- curchar = cl->name[nameind++];
+ curchar = client->name[nameind++];
if (curchar != '"' && curchar != '\\')
{
cleanname[cleanind++] = curchar;
} while (curchar != '\0');
cleanname[cleanind] = 0; // cleanind is always a valid index even at this point
- pingvalue = (int)(cl->ping * 1000.0f);
- if(cl->netconnection)
+ 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);
- str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
- if(str && *str)
+ statusstr = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
+ if(statusstr && *statusstr)
{
char *p;
const char *q;
p = qcstatus;
- for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
+ 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(cl->frags == -666) // spectator
+ if(client->frags == -666) // spectator
strlcpy(teambuf, " 0", sizeof(teambuf));
- else if(cl->colors == 0x44) // red team
+ else if(client->colors == 0x44) // red team
strlcpy(teambuf, " 1", sizeof(teambuf));
- else if(cl->colors == 0xDD) // blue team
+ else if(client->colors == 0xDD) // blue team
strlcpy(teambuf, " 2", sizeof(teambuf));
- else if(cl->colors == 0xCC) // yellow team
+ else if(client->colors == 0xCC) // yellow team
strlcpy(teambuf, " 3", sizeof(teambuf));
- else if(cl->colors == 0x99) // pink team
+ else if(client->colors == 0x99) // pink team
strlcpy(teambuf, " 4", sizeof(teambuf));
else
strlcpy(teambuf, " 0", sizeof(teambuf));
cleanname);
else
length = dpsnprintf(ptr, left, "%d %d%s \"%s\"\n",
- cl->frags,
+ client->frags,
pingvalue,
teambuf,
cleanname);
char mdfourbuf[16];
long t1, t2;
+ if (!password[0]) {
+ Con_Print("^4LOGIC 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(abs(t1 - t2) > rcon_secure_maxdiff.integer)
char mdfourbuf[16];
int i;
- if(slen < (int)(sizeof(challenge[0].string)) - 1)
+ if (!password[0]) {
+ Con_Print("^4LOGIC 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(challenge[i].time > 0)
- if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strncmp(challenge[i].string, s, sizeof(challenge[0].string) - 1))
+ 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;
// unmark challenge to prevent replay attacks
- challenge[i].time = 0;
+ challenges[i].time = 0;
return true;
}
static qboolean plaintext_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
{
+ if (!password[0]) {
+ Con_Print("^4LOGIC ERROR: RCon_Authenticate should never call the comparator with an empty password. Please report.\n");
+ return false;
+ }
+
return !strcmp(password, hash);
}
{
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])
+ 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])
+ 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))
{
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])
+ if(buf[0]) // Ignore empty entries due to leading/duplicate space.
if(comparator(peeraddress, buf, password, cs, cslen))
goto check;
userpass_start = userpass_end + 1;
}
- if(userpass_start[0])
+ 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))
{
int i, ret, clientnum, best;
double besttime;
- client_t *client;
- char *s, *string, response[1400], addressstring2[128];
+ char *string, response[1400], addressstring2[128];
static char stringbuf[16384]; // server only
qboolean islocal = (LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP);
char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
size_t sendlength, response_len;
char infostringvalue[MAX_INPUTLINE];
- char vabuf[1024];
if (!sv.active)
return false;
{
for (i = 0, best = 0, besttime = realtime;i < MAX_CHALLENGES;i++)
{
- if(challenge[i].time > 0)
- if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address))
+ if(challenges[i].time > 0)
+ if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address))
break;
- if (besttime > challenge[i].time)
- besttime = challenge[best = i].time;
+ if (besttime > challenges[i].time)
+ besttime = challenges[best = i].time;
}
// if we did not find an exact match, choose the oldest and
// update address and string
if (i == MAX_CHALLENGES)
{
i = best;
- challenge[i].address = *peeraddress;
- NetConn_BuildChallengeString(challenge[i].string, sizeof(challenge[i].string));
+ challenges[i].address = *peeraddress;
+ NetConn_BuildChallengeString(challenges[i].string, sizeof(challenges[i].string));
}
else
{
// flood control: drop if requesting challenge too often
- if(challenge[i].time > realtime - net_challengefloodblockingtimeout.value)
+ if(challenges[i].time > realtime - net_challengefloodblockingtimeout.value)
return true;
}
- challenge[i].time = realtime;
+ challenges[i].time = realtime;
// send the challenge
- dpsnprintf(response, sizeof(response), "\377\377\377\377challenge %s", challenge[i].string);
+ memcpy(response, "\377\377\377\377", 4);
+ dpsnprintf(response+4, sizeof(response)-4, "challenge %s", challenges[i].string);
response_len = strlen(response) + 1;
Crypto_ServerAppendToChallenge(string, length, response, &response_len, sizeof(response));
NetConn_Write(mysocket, response, (int)response_len, peeraddress);
}
if (length > 8 && !memcmp(string, "connect\\", 8))
{
+ char *s;
+ client_t *client;
crypto_t *crypto = Crypto_ServerGetInstance(peeraddress);
string += 7;
length -= 7;
{
// validate the challenge
for (i = 0;i < MAX_CHALLENGES;i++)
- if(challenge[i].time > 0)
- if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s))
+ if(challenges[i].time > 0)
+ if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address) && !strcmp(challenges[i].string, s))
break;
// if the challenge is not recognized, drop the packet
if (i == MAX_CHALLENGES)
{
if (developer_extra.integer)
Con_Printf("Datagram_ParseConnectionless: sending \"reject %s\" to %s.\n", sv_public_rejectreason.string, addressstring2);
- NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377reject %s", sv_public_rejectreason.string), peeraddress);
+ memcpy(response, "\377\377\377\377", 4);
+ dpsnprintf(response+4, sizeof(response)-4, "reject %s", sv_public_rejectreason.string);
+ NetConn_WriteString(mysocket, response, peeraddress);
return true;
}
}
if (length >= 5 && !memcmp(string, "rcon ", 5))
{
- int i;
+ int j;
char *s = string + 5;
char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
char password[64];
if(rcon_secure.integer > 0)
return true;
- for (i = 0;!ISWHITESPACE(*s);s++)
- if (i < (int)sizeof(password) - 1)
- password[i++] = *s;
+ for (j = 0;!ISWHITESPACE(*s);s++)
+ if (j < (int)sizeof(password) - 1)
+ password[j++] = *s;
if(ISWHITESPACE(*s) && s != endpos) // skip leading ugly space
++s;
- password[i] = 0;
+ password[j] = 0;
if (!ISWHITESPACE(password[0]))
{
const char *userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0);
int c;
int protocolnumber;
const char *protocolname;
+ client_t *knownclient;
+ client_t *newclient;
data += 4;
length -= 4;
SZ_Clear(&sv_message);
// save space for the header, filled in later
MSG_WriteLong(&sv_message, 0);
MSG_WriteByte(&sv_message, CCREP_REJECT);
- MSG_WriteString(&sv_message, va(vabuf, sizeof(vabuf), "%s\n", sv_public_rejectreason.string));
+ MSG_WriteUnterminatedString(&sv_message, sv_public_rejectreason.string);
+ MSG_WriteString(&sv_message, "\n");
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
SZ_Clear(&sv_message);
}
// see if this connect request comes from a known client
- for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++)
+ for (clientnum = 0, knownclient = svs.clients;clientnum < svs.maxclients;clientnum++, knownclient++)
{
- if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0)
+ if (knownclient->netconnection && LHNETADDRESS_Compare(peeraddress, &knownclient->netconnection->peeraddress) == 0)
{
// this is either a duplicate connection request
// or coming back from a timeout
// (if so, keep their stuff intact)
crypto_t *crypto = Crypto_ServerGetInstance(peeraddress);
- if((crypto && crypto->authenticated) || client->netconnection->crypto.authenticated)
+ if((crypto && crypto->authenticated) || knownclient->netconnection->crypto.authenticated)
{
if (developer_extra.integer)
Con_Printf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Attempt to downgrade crypto.\" to %s.\n", addressstring2);
// save space for the header, filled in later
MSG_WriteLong(&sv_message, 0);
MSG_WriteByte(&sv_message, CCREP_ACCEPT);
- MSG_WriteLong(&sv_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(client->netconnection->mysocket)));
+ MSG_WriteLong(&sv_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(knownclient->netconnection->mysocket)));
StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK));
NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress);
SZ_Clear(&sv_message);
// if client is already spawned, re-send the
// serverinfo message as they'll need it to play
- if (client->begun)
- SV_SendServerinfo(client);
+ if (knownclient->begun)
+ SV_SendServerinfo(knownclient);
return true;
}
}
break;
// find a slot for the new client
- for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++)
+ for (clientnum = 0, newclient = svs.clients;clientnum < svs.maxclients;clientnum++, newclient++)
{
netconn_t *conn;
- if (!client->active && (client->netconnection = conn = NetConn_Open(mysocket, peeraddress)) != NULL)
+ if (!newclient->active && (newclient->netconnection = conn = NetConn_Open(mysocket, peeraddress)) != NULL)
{
// connect to the client
// everything is allocated, just fill in the details
cmdname = "getservers";
extraoptions = "";
}
- dpsnprintf(request, sizeof(request), "\377\377\377\377%s %s %u empty full%s", cmdname, gamenetworkfiltername, NET_PROTOCOL_VERSION, extraoptions);
+ memcpy(request, "\377\377\377\377", 4);
+ dpsnprintf(request+4, sizeof(request)-4, "%s %s %u empty full%s", cmdname, gamenetworkfiltername, NET_PROTOCOL_VERSION, extraoptions);
// search internet
for (masternum = 0;sv_masters[masternum].name;masternum++)
Cvar_RegisterVariable(&net_connectfloodblockingtimeout);
Cvar_RegisterVariable(&net_challengefloodblockingtimeout);
Cvar_RegisterVariable(&net_getstatusfloodblockingtimeout);
+ Cvar_RegisterVariable(&net_sourceaddresscheck);
Cvar_RegisterVariable(&cl_netlocalping);
Cvar_RegisterVariable(&cl_netpacketloss_send);
Cvar_RegisterVariable(&cl_netpacketloss_receive);