+typedef qboolean (*rcon_matchfunc_t) (lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen);
+
+qboolean hmac_mdfour_time_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
+{
+ char mdfourbuf[16];
+ long t1, t2;
+
+ t1 = (long) time(NULL);
+ t2 = strtol(s, NULL, 0);
+ if(abs(t1 - t2) > rcon_secure_maxdiff.integer)
+ return false;
+
+ if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, strlen(password)))
+ return false;
+
+ return !memcmp(mdfourbuf, hash, 16);
+}
+
+qboolean hmac_mdfour_challenge_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
+{
+ char mdfourbuf[16];
+ int i;
+
+ if(slen < (int)(sizeof(challenge[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))
+ 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, strlen(password)))
+ return false;
+
+ if(memcmp(mdfourbuf, hash, 16))
+ return false;
+
+ // unmark challenge to prevent replay attacks
+ challenge[i].time = 0;
+
+ return true;
+}
+
+qboolean plaintext_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen)
+{
+ return !strcmp(password, hash);
+}
+
+/// returns a string describing the user level, or NULL for auth failure
+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;
+ char buf[MAX_INPUTLINE];
+ qboolean hasquotes;
+ qboolean restricted = false;
+ qboolean have_usernames = false;
+
+ 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])
+ if(comparator(peeraddress, buf, password, cs, cslen))
+ goto allow;
+ userpass_start = userpass_end + 1;
+ }
+ if(userpass_start[0])
+ {
+ 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])
+ if(comparator(peeraddress, buf, password, cs, cslen))
+ goto check;
+ userpass_start = userpass_end + 1;
+ }
+ if(userpass_start[0])
+ {
+ 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("%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("%srcon (username %.*s)", restricted ? "restricted " : "", (int)(userpass_startpass-userpass_start), userpass_start);
+ else
+ return va("%srcon", restricted ? "restricted " : "");
+
+ return "restricted rcon";
+}
+
+void RCon_Execute(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, const char *addressstring2, const char *userlevel, const char *s, const char *endpos)
+{
+ 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);
+ while(s != endpos)
+ {
+ size_t l = strlen(s);
+ if(l)
+ {
+ client_t *host_client_save = host_client;
+ Cmd_ExecuteString(s, src_command);
+ 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);
+ }
+}
+