+ // 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");
+#endif
+ 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);
+ 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
+ NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_QUAKEWORLD);
+ return true;
+ }
+ if (length > 2 && !memcmp(string, "n\\", 2))
+ {
+#ifdef CONFIG_MENU
+ serverlist_info_t *info;
+ int n;
+
+ // qw server status
+ if (serverlist_consoleoutput && developer_networking.integer >= 2)
+ Con_Printf("QW server status from server at %s:\n%s\n", addressstring2, string + 1);
+
+ string += 1;
+ // search the cache for this server and update it
+ n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2);
+ if (n < 0)
+ return true;
+
+ info = &serverlist_cache[n].info;
+ strlcpy(info->game, "QuakeWorld", sizeof(info->game));
+ if ((s = InfoString_GetValue(string, "*gamedir" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->mod , s, sizeof (info->mod ));else info->mod[0] = 0;
+ if ((s = InfoString_GetValue(string, "map" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->map , s, sizeof (info->map ));else info->map[0] = 0;
+ if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->name, s, sizeof (info->name));else info->name[0] = 0;
+ info->protocol = 0;
+ info->numplayers = 0; // updated below
+ info->numhumans = 0; // updated below
+ if ((s = InfoString_GetValue(string, "maxclients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s);else info->maxplayers = 0;
+ if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s);else info->gameversion = 0;
+
+ // count active players on server
+ // (we could gather more info, but we're just after the number)
+ s = strchr(string, '\n');
+ if (s)
+ {
+ s++;
+ while (s < string + length)
+ {
+ for (;s < string + length && *s != '\n';s++)
+ ;
+ if (s >= string + length)
+ break;
+ info->numplayers++;
+ info->numhumans++;
+ s++;
+ }
+ }
+
+ NetConn_ClientParsePacket_ServerList_UpdateCache(n);
+#endif
+ return true;
+ }
+ if (string[0] == 'n')
+ {
+ // 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
+ // we're done processing this packet now
+ return true;
+ }
+ // quakeworld ingame packet
+ if (fromserver && cls.protocol == PROTOCOL_QUAKEWORLD && length >= 8 && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2)
+ {
+ ret = 0;
+ CL_ParseServerMessage();
+ return ret;
+ }
+ // netquake control packets, supported for compatibility only
+ if (length >= 5 && BuffBigLong(data) == ((int)NETFLAG_CTL | length) && !ENCRYPTION_REQUIRED)
+ {
+#ifdef CONFIG_MENU
+ int n;
+ serverlist_info_t *info;
+#endif
+
+ data += 4;
+ length -= 4;
+ SZ_Clear(&cl_message);
+ SZ_Write(&cl_message, data, length);
+ MSG_BeginReading(&cl_message);
+ c = MSG_ReadByte(&cl_message);
+ switch (c)
+ {
+ case CCREP_ACCEPT:
+ if (developer_extra.integer)
+ Con_DPrintf("Datagram_ParseConnectionless: received CCREP_ACCEPT from %s.\n", addressstring2);
+ 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
+ if (length >= 6)
+ cls.proquake_servermod = MSG_ReadByte(&cl_message); // MOD_PROQUAKE
+ else
+ cls.proquake_servermod = 0;
+ if (length >= 7)
+ cls.proquake_serverversion = MSG_ReadByte(&cl_message); // version * 10
+ else
+ cls.proquake_serverversion = 0;
+ if (length >= 8)
+ cls.proquake_serverflags = MSG_ReadByte(&cl_message); // flags (mainly PQF_CHEATFREE)
+ else
+ cls.proquake_serverflags = 0;
+ if (cls.proquake_servermod == 1)
+ Con_Printf("Connected to ProQuake %.1f server, enabling precise aim\n", cls.proquake_serverversion / 10.0f);
+ // 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);
+#ifdef CONFIG_MENU
+ M_Update_Return_Reason("Accepted");
+#endif
+ NetConn_ConnectionEstablished(mysocket, &clientportaddress, PROTOCOL_QUAKE);
+ }
+ break;
+ case CCREP_REJECT:
+ 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_SERVER_INFO:
+ if (developer_extra.integer)
+ Con_DPrintf("Datagram_ParseConnectionless: received CCREP_SERVER_INFO from %s.\n", addressstring2);
+#ifdef CONFIG_MENU
+ // LadyHavoc: because the quake server may report weird addresses
+ // we just ignore it and keep the real address
+ MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring));
+ // search the cache for this server and update it
+ n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2);
+ if (n < 0)
+ break;
+
+ info = &serverlist_cache[n].info;
+ strlcpy(info->game, "Quake", sizeof(info->game));
+ strlcpy(info->mod , "", sizeof(info->mod)); // mod name is not specified
+ strlcpy(info->name, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(info->name));
+ strlcpy(info->map , MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(info->map));
+ info->numplayers = MSG_ReadByte(&cl_message);
+ info->maxplayers = MSG_ReadByte(&cl_message);
+ info->protocol = MSG_ReadByte(&cl_message);
+
+ NetConn_ClientParsePacket_ServerList_UpdateCache(n);
+#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);
+
+ Con_Printf("%s\n", MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)));
+ break;
+ case CCREP_PLAYER_INFO:
+ // we got a CCREP_PLAYER_INFO??
+ //if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: received CCREP_PLAYER_INFO from %s.\n", addressstring2);
+ break;
+ case CCREP_RULE_INFO:
+ // we got a CCREP_RULE_INFO??
+ //if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: received CCREP_RULE_INFO from %s.\n", addressstring2);
+ break;
+ default:
+ break;
+ }
+ SZ_Clear(&cl_message);
+ // we may not have liked the packet, but it was a valid control
+ // packet, so we're done processing this packet now
+ return true;
+ }
+ ret = 0;
+ if (fromserver && length >= (int)NET_HEADERSIZE && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2)
+ CL_ParseServerMessage();
+ return ret;
+}
+
+#ifdef CONFIG_MENU
+void NetConn_QueryQueueFrame(void)
+{
+ int index;
+ int queries;
+ int maxqueries;
+ double timeouttime;
+ static double querycounter = 0;
+
+ if(!net_slist_pause.integer && serverlist_paused)
+ ServerList_RebuildViewList();
+ serverlist_paused = net_slist_pause.integer != 0;
+
+ if (serverlist_querysleep)
+ return;
+
+ // apply a cool down time after master server replies,
+ // to avoid messing up the ping times on the servers
+ if (serverlist_querywaittime > host.realtime)
+ return;
+
+ // each time querycounter reaches 1.0 issue a query
+ querycounter += cl.realframetime * net_slist_queriespersecond.value;
+ maxqueries = (int)querycounter;
+ maxqueries = bound(0, maxqueries, net_slist_queriesperframe.integer);
+ querycounter -= maxqueries;
+
+ if( maxqueries == 0 ) {
+ return;
+ }
+
+ // scan serverlist and issue queries as needed
+ serverlist_querysleep = true;
+
+ timeouttime = host.realtime - net_slist_timeout.value;
+ for( index = 0, queries = 0 ; index < serverlist_cachecount && queries < maxqueries ; index++ )
+ {
+ serverlist_entry_t *entry = &serverlist_cache[ index ];
+ if( entry->query != SQS_QUERYING && entry->query != SQS_REFRESHING )
+ {
+ continue;
+ }
+
+ serverlist_querysleep = false;
+ if( entry->querycounter != 0 && entry->querytime > timeouttime )
+ {
+ continue;
+ }
+
+ if( entry->querycounter != (unsigned) net_slist_maxtries.integer )
+ {
+ lhnetaddress_t address;
+ int socket;
+
+ LHNETADDRESS_FromString(&address, entry->info.cname, 0);
+ if (entry->protocol == PROTOCOL_QUAKEWORLD)
+ {
+ for (socket = 0; socket < cl_numsockets ; socket++)
+ NetConn_WriteString(cl_sockets[socket], "\377\377\377\377status\n", &address);
+ }
+ else
+ {
+ for (socket = 0; socket < cl_numsockets ; socket++)
+ NetConn_WriteString(cl_sockets[socket], "\377\377\377\377getstatus", &address);
+ }
+
+ // update the entry fields
+ entry->querytime = host.realtime;
+ entry->querycounter++;
+
+ // if not in the slist menu we should print the server to console
+ if (serverlist_consoleoutput)
+ Con_Printf("querying %25s (%i. try)\n", entry->info.cname, entry->querycounter);
+
+ queries++;
+ }
+ else
+ {
+ // have we tried to refresh this server?
+ if( entry->query == SQS_REFRESHING ) {
+ // yes, so update the reply count (since its not responding anymore)
+ serverreplycount--;
+ if(!serverlist_paused)
+ ServerList_ViewList_Remove(entry);
+ }
+ entry->query = SQS_TIMEDOUT;
+ }
+ }
+}
+#endif
+
+void NetConn_ClientFrame(void)
+{
+ int i, length;
+ lhnetaddress_t peeraddress;
+ unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE];
+ NetConn_UpdateSockets();
+ if (cls.connect_trying && cls.connect_nextsendtime < host.realtime)
+ {
+#ifdef CONFIG_MENU
+ if (cls.connect_remainingtries == 0)
+ M_Update_Return_Reason("Connect: Waiting 10 seconds for reply");
+#endif
+ cls.connect_nextsendtime = host.realtime + 1;
+ cls.connect_remainingtries--;
+ if (cls.connect_remainingtries <= -10)
+ {
+ cls.connect_trying = false;
+#ifdef CONFIG_MENU
+ M_Update_Return_Reason("Connect: Failed");
+#endif
+ return;
+ }
+ // try challenge first (newer DP server or QW)
+ NetConn_WriteString(cls.connect_mysocket, "\377\377\377\377getchallenge", &cls.connect_address);
+ // then try netquake as a fallback (old server, or netquake)
+ SZ_Clear(&cl_message);
+ // save space for the header, filled in later
+ MSG_WriteLong(&cl_message, 0);
+ MSG_WriteByte(&cl_message, CCREQ_CONNECT);
+ MSG_WriteString(&cl_message, "QUAKE");
+ MSG_WriteByte(&cl_message, NET_PROTOCOL_VERSION);
+ // extended proquake stuff
+ MSG_WriteByte(&cl_message, 1); // mod = MOD_PROQUAKE
+ // this version matches ProQuake 3.40, the first version to support
+ // the NAT fix, and it only supports the NAT fix for ProQuake 3.40 or
+ // higher clients, so we pretend we are that version...
+ MSG_WriteByte(&cl_message, 34); // version * 10
+ MSG_WriteByte(&cl_message, 0); // flags
+ MSG_WriteLong(&cl_message, 0); // password
+ // write the packetsize now...
+ StoreBigLong(cl_message.data, NETFLAG_CTL | (cl_message.cursize & NETFLAG_LENGTH_MASK));
+ NetConn_Write(cls.connect_mysocket, cl_message.data, cl_message.cursize, &cls.connect_address);
+ SZ_Clear(&cl_message);
+ }
+ for (i = 0;i < cl_numsockets;i++)
+ {
+ while (cl_sockets[i] && (length = NetConn_Read(cl_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0)
+ {
+// R_TimeReport("clientreadnetwork");
+ NetConn_ClientParsePacket(cl_sockets[i], readbuffer, length, &peeraddress);
+// R_TimeReport("clientparsepacket");
+ }
+ }
+#ifdef CONFIG_MENU
+ NetConn_QueryQueueFrame();
+#endif
+ if (cls.netcon && host.realtime > cls.netcon->timeout && !sv.active)
+ CL_DisconnectEx(true, "Connection timed out");
+}
+
+static void NetConn_BuildChallengeString(char *buffer, int bufferlength)
+{
+ int i;
+ char c;
+ for (i = 0;i < bufferlength - 1;i++)
+ {
+ do
+ {
+ c = rand () % (127 - 33) + 33;
+ } while (c == '\\' || c == ';' || c == '"' || c == '%' || c == '/');
+ buffer[i] = c;
+ }
+ buffer[i] = 0;
+}
+
+/// (div0) build the full response only if possible; better a getinfo response than no response at all if getstatus won't fit
+static qbool NetConn_BuildStatusResponse(const char* challenge, char* out_msg, size_t out_size, qbool fullstatus)
+{
+ prvm_prog_t *prog = SVVM_prog;
+ char qcstatus[256];
+ unsigned int nb_clients = 0, nb_bots = 0, i;
+ int length;
+ char teambuf[3];
+ const char *crypto_idstring;
+ const char *worldstatusstr;
+
+ // How many clients are there?
+ for (i = 0;i < (unsigned int)svs.maxclients;i++)
+ {
+ if (svs.clients[i].active)
+ {
+ nb_clients++;
+ if (!svs.clients[i].netconnection)
+ nb_bots++;
+ }
+ }
+
+ *qcstatus = 0;
+ worldstatusstr = PRVM_GetString(prog, PRVM_serverglobalstring(worldstatus));
+ if(worldstatusstr && *worldstatusstr)
+ {
+ char *p;
+ const char *q;
+ p = qcstatus;
+ for(q = worldstatusstr; *q && (size_t)(p - qcstatus) < (sizeof(qcstatus) - 1); ++q)
+ if(*q != '\\' && *q != '\n')
+ *p++ = *q;
+ *p = 0;
+ }
+
+ /// \TODO: we should add more information for the full status string
+ crypto_idstring = Crypto_GetInfoResponseDataString();
+ length = dpsnprintf(out_msg, out_size,
+ "\377\377\377\377%s\x0A"
+ "\\gamename\\%s\\modname\\%s\\gameversion\\%d\\sv_maxclients\\%d"
+ "\\clients\\%d\\bots\\%d\\mapname\\%s\\hostname\\%s\\protocol\\%d"
+ "%s%s"
+ "%s%s"
+ "%s%s"
+ "%s",
+ fullstatus ? "statusResponse" : "infoResponse",
+ gamenetworkfiltername, com_modname, gameversion.integer, svs.maxclients,
+ nb_clients, nb_bots, sv.worldbasename, hostname.string, NET_PROTOCOL_VERSION,
+ *qcstatus ? "\\qcstatus\\" : "", qcstatus,
+ challenge ? "\\challenge\\" : "", challenge ? challenge : "",
+ crypto_idstring ? "\\d0_blind_id\\" : "", crypto_idstring ? crypto_idstring : "",
+ fullstatus ? "\n" : "");
+
+ // 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;
+ }
+
+ restricted = true;
+ have_usernames = false;
+ userpass_start = rcon_restricted_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 check;
+ 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 check;
+ }
+
+ return NULL; // DENIED
+
+check:
+ for(text = s; text != endpos; ++text)
+ if((signed char) *text > 0 && ((signed char) *text < (signed char) ' ' || *text == ';'))
+ return NULL; // block possible exploits against the parser/alias expansion
+
+ while(s != endpos)
+ {
+ size_t l = strlen(s);
+ if(l)
+ {
+ hasquotes = (strchr(s, '"') != NULL);
+ // sorry, we can't allow these substrings in wildcard expressions,
+ // as they can mess with the argument counts
+ text = rcon_restricted_commands.string;
+ while(COM_ParseToken_Console(&text))
+ {
+ // com_token now contains a pattern to check for...
+ if(strchr(com_token, '*') || strchr(com_token, '?')) // wildcard expression, * can only match a SINGLE argument
+ {
+ if(!hasquotes)
+ if(matchpattern_with_separator(s, com_token, true, " ", true)) // note how we excluded tab, newline etc. above
+ goto match;
+ }
+ else if(strchr(com_token, ' ')) // multi-arg expression? must match in whole
+ {
+ if(!strcmp(com_token, s))
+ goto match;
+ }
+ else // single-arg expression? must match the beginning of the command
+ {
+ if(!strcmp(com_token, s))
+ goto match;
+ if(!memcmp(va(vabuf, sizeof(vabuf), "%s ", com_token), s, strlen(com_token) + 1))
+ goto match;
+ }
+ }
+ // if we got here, nothing matched!
+ return NULL;
+ }
+match:
+ s += l + 1;
+ }
+
+allow:
+ userpass_startpass = strchr(userpass_start, ':');
+ if(have_usernames && userpass_startpass && userpass_startpass < userpass_end)
+ return va(vabuf, sizeof(vabuf), "%srcon (username %.*s)", restricted ? "restricted " : "", (int)(userpass_startpass-userpass_start), userpass_start);
+
+ return va(vabuf, sizeof(vabuf), "%srcon", restricted ? "restricted " : "");
+}
+
+static void RCon_Execute(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, const char *addressstring2, const char *userlevel, const char *s, const char *endpos, qbool proquakeprotocol)
+{
+ if(userlevel)
+ {
+ // looks like a legitimate rcon command with the correct password
+ const char *s_ptr = s;
+ Con_Printf("server received %s command from %s: ", userlevel, host_client ? host_client->name : addressstring2);
+ while(s_ptr != endpos)
+ {
+ size_t l = strlen(s_ptr);
+ if(l)
+ Con_Printf(" %s;", s_ptr);
+ s_ptr += l + 1;
+ }
+ Con_Printf("\n");
+
+ if (!host_client || !host_client->netconnection || LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
+ Con_Rcon_Redirect_Init(mysocket, peeraddress, proquakeprotocol);
+ while(s != endpos)
+ {
+ size_t l = strlen(s);
+ if(l)
+ {
+ client_t *host_client_save = host_client;
+ Cmd_ExecuteString(cmd_local, s, src_local, true);
+ host_client = host_client_save;
+ // in case it is a command that changes host_client (like restart)
+ }
+ s += l + 1;
+ }
+ Con_Rcon_Redirect_End();
+ }
+ else
+ {
+ Con_Printf("server denied rcon access to %s\n", host_client ? host_client->name : addressstring2);
+ }
+}
+
+static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress)
+{
+ int i, ret, clientnum, best;
+ double besttime;
+ char *string, response[2800], addressstring2[128];
+ static char stringbuf[16384]; // server only
+ qbool islocal = (LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP);
+ char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE];
+ size_t sendlength, response_len;
+ char infostringvalue[MAX_INPUTLINE];
+
+ if (!sv.active)
+ return false;
+
+ // convert the address to a string incase we need it
+ LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true);
+
+ // see if we can identify the sender as a local player
+ // (this is necessary for rcon to send a reliable reply if the client is
+ // actually on the server, not sending remotely)
+ for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
+ if (host_client->netconnection && host_client->netconnection->mysocket == mysocket && !LHNETADDRESS_Compare(&host_client->netconnection->peeraddress, peeraddress))
+ break;
+ if (i == svs.maxclients)
+ host_client = NULL;
+
+ if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255)
+ {
+ // received a command string - strip off the packaging and put it
+ // into our string buffer with NULL termination
+ data += 4;
+ length -= 4;
+ length = min(length, (int)sizeof(stringbuf) - 1);
+ memcpy(stringbuf, data, length);
+ stringbuf[length] = 0;
+ string = stringbuf;
+
+ if (developer_extra.integer)
+ {
+ Con_Printf("NetConn_ServerParsePacket: %s sent us a command:\n", addressstring2);
+ Com_HexDumpToConsole(data, length);
+ }
+
+ sendlength = sizeof(senddata) - 4;
+ switch(Crypto_ServerParsePacket(string, length, senddata+4, &sendlength, peeraddress))
+ {
+ case CRYPTO_NOMATCH:
+ // nothing to do
+ break;
+ case CRYPTO_MATCH:
+ if(sendlength)
+ {
+ memcpy(senddata, "\377\377\377\377", 4);
+ NetConn_Write(mysocket, senddata, (int)sendlength+4, peeraddress);
+ }
+ break;
+ case CRYPTO_DISCARD:
+ if(sendlength)
+ {
+ memcpy(senddata, "\377\377\377\377", 4);
+ NetConn_Write(mysocket, senddata, (int)sendlength+4, peeraddress);
+ }
+ return true;
+ break;
+ case CRYPTO_REPLACE:
+ string = senddata+4;
+ length = (int)sendlength;
+ break;
+ }
+
+ if (length >= 12 && !memcmp(string, "getchallenge", 12) && (islocal || sv_public.integer > -3))
+ {
+ for (i = 0, best = 0, besttime = host.realtime;i < MAX_CHALLENGES;i++)
+ {
+ if(challenges[i].time > 0)
+ if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address))
+ break;
+ 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;
+ challenges[i].address = *peeraddress;
+ NetConn_BuildChallengeString(challenges[i].string, sizeof(challenges[i].string));
+ }
+ else
+ {
+ // flood control: drop if requesting challenge too often
+ if(challenges[i].time > host.realtime - net_challengefloodblockingtimeout.value)
+ return true;
+ }
+ challenges[i].time = host.realtime;
+ // send the challenge
+ 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);
+ return true;
+ }
+ if (length > 8 && !memcmp(string, "connect\\", 8))
+ {
+ char *s;
+ client_t *client;
+ crypto_t *crypto = Crypto_ServerGetInstance(peeraddress);
+ string += 7;
+ length -= 7;
+
+ if(crypto && crypto->authenticated)
+ {
+ // no need to check challenge
+ if(crypto_developer.integer)
+ {
+ Con_Printf("%s connection to %s is being established: client is %s@%s%.*s, I am %.*s@%s%.*s\n",
+ crypto->use_aes ? "Encrypted" : "Authenticated",
+ addressstring2,
+ crypto->client_idfp[0] ? crypto->client_idfp : "-",
+ (crypto->client_issigned || !crypto->client_keyfp[0]) ? "" : "~",
+ crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-",
+ crypto_keyfp_recommended_length, crypto->server_idfp[0] ? crypto->server_idfp : "-",
+ (crypto->server_issigned || !crypto->server_keyfp[0]) ? "" : "~",
+ crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-"
+ );
+ }
+ }
+ else
+ {
+ if ((s = InfoString_GetValue(string, "challenge", infostringvalue, sizeof(infostringvalue))))
+ {
+ // validate the challenge
+ for (i = 0;i < MAX_CHALLENGES;i++)
+ 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)
+ return true;
+ }
+ }
+
+ if((s = InfoString_GetValue(string, "message", infostringvalue, sizeof(infostringvalue))))
+ Con_DPrintf("Connecting client %s sent us the message: %s\n", addressstring2, s);
+
+ if(!(islocal || sv_public.integer > -2))
+ {
+ if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: sending \"reject %s\" to %s.\n", sv_public_rejectreason.string, addressstring2);
+ 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;
+ }
+
+ // check engine protocol
+ if(!(s = InfoString_GetValue(string, "protocol", infostringvalue, sizeof(infostringvalue))) || strcmp(s, "darkplaces 3"))
+ {
+ if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: sending \"reject Wrong game protocol.\" to %s.\n", addressstring2);
+ NetConn_WriteString(mysocket, "\377\377\377\377reject Wrong game protocol.", peeraddress);
+ return true;
+ }
+
+ // see if this is a duplicate connection request or a disconnected
+ // client who is rejoining to the same client slot
+ for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++)
+ {
+ if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0)
+ {
+ // this is a known client...
+ if(crypto && crypto->authenticated)
+ {
+ // reject if changing key!
+ if(client->netconnection->crypto.authenticated)
+ {
+ if(
+ strcmp(client->netconnection->crypto.client_idfp, crypto->client_idfp)
+ ||
+ strcmp(client->netconnection->crypto.server_idfp, crypto->server_idfp)
+ ||
+ strcmp(client->netconnection->crypto.client_keyfp, crypto->client_keyfp)
+ ||
+ strcmp(client->netconnection->crypto.server_keyfp, crypto->server_keyfp)
+ )
+ {
+ if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to change key of crypto.\" to %s.\n", addressstring2);
+ NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to change key of crypto.", peeraddress);
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // reject if downgrading!
+ if(client->netconnection->crypto.authenticated)
+ {
+ if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to downgrade crypto.\" to %s.\n", addressstring2);
+ NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to downgrade crypto.", peeraddress);
+ return true;
+ }
+ }
+ if (client->begun)
+ {
+ // client crashed and is coming back,
+ // keep their stuff intact
+ if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", addressstring2);
+ NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress);
+ if(crypto && crypto->authenticated)
+ Crypto_FinishInstance(&client->netconnection->crypto, crypto);
+ SV_SendServerinfo(client);
+ }
+ else
+ {
+ // client is still trying to connect,
+ // so we send a duplicate reply
+ if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: sending duplicate accept to %s.\n", addressstring2);
+ if(crypto && crypto->authenticated)
+ Crypto_FinishInstance(&client->netconnection->crypto, crypto);
+ NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress);
+ }
+ return true;
+ }
+ }
+
+ if (NetConn_PreventFlood(peeraddress, sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]), net_connectfloodblockingtimeout.value, true))
+ return true;
+
+ // find an empty client slot for this new client
+ for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++)
+ {
+ netconn_t *conn;
+ if (!client->active && (conn = NetConn_Open(mysocket, peeraddress)))
+ {
+ // allocated connection
+ if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", conn->address);
+ NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress);
+ // now set up the client
+ if(crypto && crypto->authenticated)
+ Crypto_FinishInstance(&conn->crypto, crypto);
+ SV_ConnectClient(clientnum, conn);
+ NetConn_Heartbeat(1);
+ return true;
+ }
+ }
+
+ // no empty slots found - server is full
+ if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: sending \"reject Server is full.\" to %s.\n", addressstring2);
+ NetConn_WriteString(mysocket, "\377\377\377\377reject Server is full.", peeraddress);
+
+ return true;
+ }
+ if (length >= 7 && !memcmp(string, "getinfo", 7) && (islocal || sv_public.integer > -1))
+ {
+ const char *challenge = NULL;
+
+ if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false))
+ return true;
+
+ // If there was a challenge in the getinfo message
+ if (length > 8 && string[7] == ' ')
+ challenge = string + 8;
+
+ if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), false))
+ {
+ if (developer_extra.integer)
+ Con_DPrintf("Sending reply to master %s - %s\n", addressstring2, response);
+ NetConn_WriteString(mysocket, response, peeraddress);
+ }
+ return true;
+ }
+ if (length >= 9 && !memcmp(string, "getstatus", 9) && (islocal || sv_public.integer > -1))
+ {
+ const char *challenge = NULL;
+
+ if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false))
+ return true;
+
+ // If there was a challenge in the getinfo message
+ if (length > 10 && string[9] == ' ')
+ challenge = string + 10;
+
+ if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), true))
+ {
+ if (developer_extra.integer)
+ Con_DPrintf("Sending reply to client %s - %s\n", addressstring2, response);
+ NetConn_WriteString(mysocket, response, peeraddress);
+ }
+ return true;
+ }
+ if (length >= 37 && !memcmp(string, "srcon HMAC-MD4 TIME ", 20))
+ {
+ char *password = string + 20;
+ char *timeval = string + 37;
+ char *s = strchr(timeval, ' ');
+ char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
+ const char *userlevel;
+
+ if(rcon_secure.integer > 1)
+ return true;
+
+ if(!s)
+ return true; // invalid packet
+ ++s;
+
+ userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_time_matching, timeval, endpos - timeval - 1); // not including the appended \0 into the HMAC
+ RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false);
+ return true;
+ }
+ if (length >= 42 && !memcmp(string, "srcon HMAC-MD4 CHALLENGE ", 25))
+ {
+ char *password = string + 25;
+ char *challenge = string + 42;
+ char *s = strchr(challenge, ' ');
+ char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it
+ const char *userlevel;
+ if(!s)
+ return true; // invalid packet
+ ++s;
+
+ userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_challenge_matching, challenge, endpos - challenge - 1); // not including the appended \0 into the HMAC
+ RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false);
+ return true;
+ }
+ if (length >= 5 && !memcmp(string, "rcon ", 5))
+ {
+ 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 (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[j] = 0;
+ if (!ISWHITESPACE(password[0]))
+ {
+ const char *userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0);
+ RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false);
+ }
+ return true;
+ }
+ if (!strncmp(string, "extResponse ", 12))
+ {
+ ++sv_net_extresponse_count;
+ if(sv_net_extresponse_count > NET_EXTRESPONSE_MAX)
+ sv_net_extresponse_count = NET_EXTRESPONSE_MAX;
+ sv_net_extresponse_last = (sv_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX;
+ dpsnprintf(sv_net_extresponse[sv_net_extresponse_last], sizeof(sv_net_extresponse[sv_net_extresponse_last]), "'%s' %s", addressstring2, string + 12);
+ return true;
+ }
+ if (!strncmp(string, "ping", 4))
+ {
+ if (developer_extra.integer)
+ Con_DPrintf("Received ping from %s, sending ack\n", addressstring2);
+ NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress);
+ return true;
+ }
+ if (!strncmp(string, "ack", 3))
+ return true;
+ // we may not have liked the packet, but it was a command packet, so
+ // we're done processing this packet now
+ return true;
+ }
+ // netquake control packets, supported for compatibility only, and only
+ // when running game protocols that are normally served via this connection
+ // protocol
+ // (this protects more modern protocols against being used for
+ // Quake packet flood Denial Of Service attacks)
+ if (length >= 5 && (i = BuffBigLong(data)) && (i & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (i & NETFLAG_LENGTH_MASK) == length && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) && !ENCRYPTION_REQUIRED)
+ {
+ int c;
+ int protocolnumber;
+ const char *protocolname;
+ client_t *knownclient;
+ client_t *newclient;
+ data += 4;
+ length -= 4;
+ SZ_Clear(&sv_message);
+ SZ_Write(&sv_message, data, length);
+ MSG_BeginReading(&sv_message);
+ c = MSG_ReadByte(&sv_message);
+ switch (c)
+ {
+ case CCREQ_CONNECT:
+ if (developer_extra.integer)
+ Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_CONNECT from %s.\n", addressstring2);
+ if(!(islocal || sv_public.integer > -2))
+ {
+ if (developer_extra.integer)
+ Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"%s\" to %s.\n", sv_public_rejectreason.string, addressstring2);
+ SZ_Clear(&sv_message);
+ // save space for the header, filled in later
+ MSG_WriteLong(&sv_message, 0);
+ MSG_WriteByte(&sv_message, CCREP_REJECT);
+ 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);
+ break;
+ }
+
+ protocolname = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring));
+ protocolnumber = MSG_ReadByte(&sv_message);
+ if (strcmp(protocolname, "QUAKE") || protocolnumber != NET_PROTOCOL_VERSION)
+ {
+ if (developer_extra.integer)
+ Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Incompatible version.\" to %s.\n", addressstring2);
+ 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, "Incompatible version.\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);
+ break;
+ }
+
+ // see if this connect request comes from a known client
+ for (clientnum = 0, knownclient = svs.clients;clientnum < svs.maxclients;clientnum++, knownclient++)
+ {
+ 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) || knownclient->netconnection->crypto.authenticated)
+ {
+ if (developer_extra.integer)
+ Con_Printf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Attempt to downgrade crypto.\" to %s.\n", addressstring2);
+ 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, "Attempt to downgrade crypto.\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);
+ return true;
+ }
+
+ // send a reply
+ if (developer_extra.integer)
+ Con_DPrintf("Datagram_ParseConnectionless: sending duplicate CCREP_ACCEPT to %s.\n", addressstring2);
+ SZ_Clear(&sv_message);
+ // 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(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 (knownclient->begun)
+ SV_SendServerinfo(knownclient);
+ return true;
+ }
+ }
+
+ // this is a new client, check for connection flood
+ if (NetConn_PreventFlood(peeraddress, sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]), net_connectfloodblockingtimeout.value, true))
+ break;
+
+ // find a slot for the new client
+ for (clientnum = 0, newclient = svs.clients;clientnum < svs.maxclients;clientnum++, newclient++)