#include "libcurl.h"
cvar_t crypto_developer = {CVAR_SAVE, "crypto_developer", "0", "print extra info about crypto handshake"};
+cvar_t crypto_aeslevel = {CVAR_SAVE, "crypto_aeslevel", "1", "whether to support AES encryption in authenticated connections (0 = no, 1 = supported, 2 = requested, 3 = required)"};
+
cvar_t crypto_servercpupercent = {CVAR_SAVE, "crypto_servercpupercent", "10", "allowed crypto CPU load in percent for server operation (0 = no limit, faster)"};
cvar_t crypto_servercpumaxtime = {CVAR_SAVE, "crypto_servercpumaxtime", "0.01", "maximum allowed crypto CPU time per frame (0 = no limit)"};
cvar_t crypto_servercpudebug = {CVAR_SAVE, "crypto_servercpudebug", "0", "print statistics about time usage by crypto"};
static double crypto_servercpu_accumulator = 0;
static double crypto_servercpu_lastrealtime = 0;
-cvar_t crypto_aeslevel = {CVAR_SAVE, "crypto_aeslevel", "1", "whether to support AES encryption in authenticated connections (0 = no, 1 = supported, 2 = requested, 3 = required)"};
+
+extern cvar_t net_sourceaddresscheck;
+
int crypto_keyfp_recommended_length;
static const char *crypto_idstring = NULL;
static char crypto_idstring_buf[512];
+
#define PROTOCOL_D0_BLIND_ID FOURCC_D0PK
#define PROTOCOL_VLEN (('v' << 0) | ('l' << 8) | ('e' << 16) | ('n' << 24))
break;
// if the challenge is not recognized, drop the packet
if (i == MAX_CHALLENGES) // challenge mismatch is silent
- return CRYPTO_DISCARD; // pre-challenge: rather be silent
+ return Crypto_SoftServerError(data_out, len_out, "missing challenge in connect");
crypto = Crypto_ServerFindInstance(peeraddress, false);
if(!crypto || !crypto->authenticated)
id = (cnt ? atoi(cnt) : -1);
cnt = InfoString_GetValue(string + 4, "cnt", infostringvalue, sizeof(infostringvalue));
if(!cnt)
- return CRYPTO_DISCARD; // pre-challenge: rather be silent
+ return Crypto_SoftServerError(data_out, len_out, "missing cnt in d0pk");
GetUntilNul(&data_in, &len_in);
if(!data_in)
- return CRYPTO_DISCARD; // pre-challenge: rather be silent
+ return Crypto_SoftServerError(data_out, len_out, "missing appended data in d0pk");
if(!strcmp(cnt, "0"))
{
int i;
if (!(s = InfoString_GetValue(string + 4, "challenge", infostringvalue, sizeof(infostringvalue))))
- return CRYPTO_DISCARD; // pre-challenge: rather be silent
+ return Crypto_SoftServerError(data_out, len_out, "missing challenge in d0pk\\0");
// 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))
break;
// if the challenge is not recognized, drop the packet
- if (i == MAX_CHALLENGES) // challenge mismatch is silent
- return CRYPTO_DISCARD; // pre-challenge: rather be silent
+ if (i == MAX_CHALLENGES)
+ return Crypto_SoftServerError(data_out, len_out, "invalid challenge in d0pk\\0");
if (!(s = InfoString_GetValue(string + 4, "aeslevel", infostringvalue, sizeof(infostringvalue))))
aeslevel = 0; // not supported
static int Crypto_SoftClientError(char *data_out, size_t *len_out, const char *msg)
{
*len_out = 0;
- Con_Printf("%s\n", msg);
+ Con_DPrintf("%s\n", msg);
return CRYPTO_DISCARD;
}
int wantserver_aeslevel = 0;
qboolean wantserver_issigned = false;
+ // Must check the source IP here, if we want to prevent other servers' replies from falsely advancing the crypto state, preventing successful connect to the real server.
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address))
+ return Crypto_SoftClientError(data_out, len_out, "challenge message from wrong server");
+
// if we have a stored host key for the server, assume serverid to already be selected!
// (the loop will refuse to overwrite this one then)
wantserver_idfp[0] = 0;
GetUntilNul(&data_in, &len_in);
if(!data_in)
return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present") :
- (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) :
+ (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)") :
CRYPTO_NOMATCH;
// FTEQW extension protocol
if(!vlen_blind_id_ptr)
return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though authentication is required") :
- (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) :
+ (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)") :
CRYPTO_NOMATCH;
data_in = vlen_blind_id_ptr;
default: // dummy, never happens, but to make gcc happy...
case 0:
if(wantserver_aeslevel >= 3)
- return Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL);
+ return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)");
CDATA->wantserver_aes = false;
break;
case 1:
break;
case 3:
if(wantserver_aeslevel <= 0)
- return Crypto_ServerError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)", NULL);
+ return Crypto_ClientError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)");
CDATA->wantserver_aes = true;
break;
}
data_out_p += *len_out;
*len_out = data_out_p - data_out;
}
-
return CRYPTO_DISCARD;
}
else
if(wantserver_idfp[0]) // if we know a host key, honor its encryption setting
if(wantserver_aeslevel >= 3)
return Crypto_ClientError(data_out, len_out, "Server insists on encryption, but neither can authenticate to the other");
- return (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) :
+ return (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)") :
CRYPTO_NOMATCH;
}
}
{
const char *cnt;
int id;
+
+ // Must check the source IP here, if we want to prevent other servers' replies from falsely advancing the crypto state, preventing successful connect to the real server.
+ if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address))
+ return Crypto_SoftClientError(data_out, len_out, "d0pk\\ message from wrong server");
+
cnt = InfoString_GetValue(string + 4, "id", infostringvalue, sizeof(infostringvalue));
id = (cnt ? atoi(cnt) : -1);
cnt = InfoString_GetValue(string + 4, "cnt", infostringvalue, sizeof(infostringvalue));
{
if(id >= 0)
if(CDATA->cdata_id != id)
- return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
+ return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
if(CDATA->next_step != 1)
return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step));
if(id >= 0)
if(CDATA->cdata_id != id)
- return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
+ return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
if(CDATA->next_step != 3)
return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step));
if(id >= 0)
if(CDATA->cdata_id != id)
- return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
+ return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id));
if(CDATA->next_step != 5)
return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step));
{
int n;
const char *e;
- lhnetaddress_t to;
lhnetsocket_t *mysocket;
- char peer_address[64];
if (Cmd_Argc() == 1)
{
n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
if (cls.netcon)
- {
- InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
- }
+ cls.rcon_address = cls.netcon->peeraddress;
else
{
if (!rcon_address.string[0])
Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
return;
}
- strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
+ LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
}
- LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
- mysocket = NetConn_ChooseClientSocketForAddress(&to);
+ mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
if (mysocket)
{
sizebuf_t buf;
MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
MSG_WriteString(&buf, Cmd_Args());
StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
- NetConn_Write(mysocket, buf.data, buf.cursize, &to);
+ NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
SZ_Clear(&buf);
}
}
{
int i, n;
const char *e;
- lhnetaddress_t to;
lhnetsocket_t *mysocket;
char vabuf[1024];
n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
if (cls.netcon)
- to = cls.netcon->peeraddress;
+ cls.rcon_address = cls.netcon->peeraddress;
else
{
if (!rcon_address.string[0])
Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
return;
}
- LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
+ LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
}
- mysocket = NetConn_ChooseClientSocketForAddress(&to);
+ mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
if (mysocket && Cmd_Args()[0])
{
// simply put together the rcon packet and send it
}
for (i = 0;i < MAX_RCONS;i++)
if(cls.rcon_commands[i][0])
- if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
+ if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
break;
++cls.rcon_trying;
if(i >= MAX_RCONS)
- NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
+ NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
- cls.rcon_addresses[cls.rcon_ringpos] = to;
+ cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
}
{
buf[40] = ' ';
strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
- NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &to);
+ NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
}
}
else
{
- NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
+ NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &cls.rcon_address);
}
}
}
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)"};
{
// 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
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");
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);
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);