From: divverent <divverent@d7cf8633-e32d-0410-b094-e92efae38249>
Date: Fri, 2 Oct 2009 08:19:58 +0000 (+0000)
Subject: rcon_secure: alternative getchallenge-based authentication that doesn't rely on the... 
X-Git-Tag: xonotic-v0.1.0preview~1355
X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=69148a580e7cbb280bb5696cf7c9bc4c3b9b9605;p=xonotic%2Fdarkplaces.git

rcon_secure: alternative getchallenge-based authentication that doesn't rely on the system time (server side only at the moment)


git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@9278 d7cf8633-e32d-0410-b094-e92efae38249
---

diff --git a/netconn.c b/netconn.c
index ea6e92c0..80355210 100755
--- a/netconn.c
+++ b/netconn.c
@@ -2292,9 +2292,9 @@ void NetConn_ClearConnectFlood(lhnetaddress_t *peeraddress)
 	}
 }
 
-typedef qboolean (*rcon_matchfunc_t) (const char *password, const char *hash, const char *s, int slen);
+typedef qboolean (*rcon_matchfunc_t) (lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen);
 
-qboolean hmac_mdfour_matching(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;
@@ -2310,21 +2310,55 @@ qboolean hmac_mdfour_matching(const char *password, const char *hash, const char
 	return !memcmp(mdfourbuf, hash, 16);
 }
 
-qboolean plaintext_matching(const char *password, const char *hash, const char *s, int slen)
+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 (!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
+	// FIXME as there is currently no unmark facility, let's invalidate it
+	// as much as possible
+	challenge[i].string[0] = '\\'; // not allowed in infostrings, so connects cannot match
+	NetConn_BuildChallengeString(challenge[i].string + 1, sizeof(challenge[i].string) - 1);
+	challenge[i].time = 0;
+	LHNETADDRESS_FromString(&challenge[i].address, "local:42", 42); // no rcon will come from there for sure
+	challenge[i].address = *peeraddress;
+
+	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(const char *password, const char *s, const char *endpos, rcon_matchfunc_t comparator, const char *cs, int cslen)
+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;
 	qboolean hasquotes;
 
-	if(comparator(rcon_password.string, password, cs, cslen))
+	if(comparator(peeraddress, rcon_password.string, password, cs, cslen))
 		return "rcon";
 	
-	if(!comparator(rcon_restricted_password.string, password, cs, cslen))
+	if(!comparator(peeraddress, rcon_restricted_password.string, password, cs, cslen))
 		return NULL;
 
 	for(text = s; text != endpos; ++text)
@@ -2595,11 +2629,30 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
 			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);
+			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(password, s, endpos, hmac_mdfour_matching, timeval, endpos - timeval - 1); // not including the appended \0 into the HMAC
+			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);
 			return true;
 		}
@@ -2621,7 +2674,7 @@ static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *dat
 			password[i] = 0;
 			if (!ISWHITESPACE(password[0]))
 			{
-				const char *userlevel = RCon_Authenticate(password, s, endpos, plaintext_matching, NULL, 0);
+				const char *userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0);
 				RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos);
 			}
 			return true;