+
+bad:
+ SV_VM_End();
+ return false;
+}
+
+static qboolean NetConn_PreventConnectFlood(lhnetaddress_t *peeraddress)
+{
+ int floodslotnum, bestfloodslotnum;
+ double bestfloodtime;
+ lhnetaddress_t noportpeeraddress;
+ // see if this is a connect flood
+ noportpeeraddress = *peeraddress;
+ LHNETADDRESS_SetPort(&noportpeeraddress, 0);
+ bestfloodslotnum = 0;
+ bestfloodtime = sv.connectfloodaddresses[bestfloodslotnum].lasttime;
+ for (floodslotnum = 0;floodslotnum < MAX_CONNECTFLOODADDRESSES;floodslotnum++)
+ {
+ if (bestfloodtime >= sv.connectfloodaddresses[floodslotnum].lasttime)
+ {
+ bestfloodtime = sv.connectfloodaddresses[floodslotnum].lasttime;
+ bestfloodslotnum = floodslotnum;
+ }
+ if (sv.connectfloodaddresses[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &sv.connectfloodaddresses[floodslotnum].address) == 0)
+ {
+ // this address matches an ongoing flood address
+ if (realtime < sv.connectfloodaddresses[floodslotnum].lasttime + net_connectfloodblockingtimeout.value)
+ {
+ // renew the ban on this address so it does not expire
+ // until the flood has subsided
+ sv.connectfloodaddresses[floodslotnum].lasttime = 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
+ sv.connectfloodaddresses[bestfloodslotnum].address = noportpeeraddress;
+ sv.connectfloodaddresses[bestfloodslotnum].lasttime = realtime;
+ //Con_Printf("Flood detection initiated!\n");
+ return false;
+}
+
+void NetConn_ClearConnectFlood(lhnetaddress_t *peeraddress)
+{
+ int floodslotnum;
+ lhnetaddress_t noportpeeraddress;
+ // see if this is a connect flood
+ noportpeeraddress = *peeraddress;
+ LHNETADDRESS_SetPort(&noportpeeraddress, 0);
+ for (floodslotnum = 0;floodslotnum < MAX_CONNECTFLOODADDRESSES;floodslotnum++)
+ {
+ if (sv.connectfloodaddresses[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &sv.connectfloodaddresses[floodslotnum].address) == 0)
+ {
+ // this address matches an ongoing flood address
+ // remove the ban
+ sv.connectfloodaddresses[floodslotnum].address.addresstype = LHNETADDRESSTYPE_NONE;
+ sv.connectfloodaddresses[floodslotnum].lasttime = 0;
+ //Con_Printf("Flood cleared!\n");
+ }
+ }
+}
+
+// returns a string describing the user level, or NULL for auth failure
+const char *RCon_Authenticate(const char *password, const char *s, const char *endpos)
+{
+ const char *text;
+ qboolean hasquotes;
+
+ if(!strcmp(rcon_password.string, password))
+ return "rcon";
+
+ if(strcmp(rcon_restricted_password.string, password))
+ return NULL;
+
+ for(text = s; text != endpos; ++text)
+ if(*text > 0 && (*text < ' ' || *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("%s ", com_token), s, strlen(com_token) + 1))
+ goto match;
+ }
+ }
+ // if we got here, nothing matched!
+ return NULL;
+ }
+match:
+ s += l + 1;
+ }
+
+ return "restricted rcon";