openmohaa/code/gamespy/qr2/qr2.c
2023-06-22 18:32:15 +02:00

1753 lines
52 KiB
C

/********
INCLUDES
********/
#include "../common/gsCommon.h"
#include "../common/gsAvailable.h"
#include "qr2.h"
#include "qr2regkeys.h"
#include "../natneg/natneg.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __MWERKS__ // Codewarrior requires function prototypes
qr2_error_t qr2_initW(/*[out]*/qr2_t *qrec, const unsigned short *ip, int baseport, const unsigned short *gamename,
const unsigned short *secret_key, int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback,
qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback, qr2_keylistcallback_t key_list_callback,
qr2_countcallback_t playerteam_count_callback, qr2_adderrorcallback_t adderror_callback, void *userdata);
qr2_error_t qr2_init_socketW(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const unsigned short *gamename,
const unsigned short *secret_key, int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback,
qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback, qr2_keylistcallback_t key_list_callback,
qr2_countcallback_t playerteam_count_callback, qr2_adderrorcallback_t adderror_callback, void *userdata);
qr2_error_t qr2_init_socketA(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const char *gamename, const char *secret_key,
int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback,
qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback,
qr2_keylistcallback_t key_list_callback, qr2_countcallback_t playerteam_count_callback,
qr2_adderrorcallback_t adderror_callback, void *userdata);
qr2_error_t qr2_initA(/*[out]*/qr2_t *qrec, const char *ip, int baseport, const char *gamename, const char *secret_key,
int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback,
qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback,
qr2_keylistcallback_t key_list_callback, qr2_countcallback_t playerteam_count_callback,
qr2_adderrorcallback_t adderror_callback, void *userdata);
#endif
/********
DEFINES
********/
#define MASTER_PORT 27900
#define MASTER_ADDR "master." GSI_DOMAIN_NAME
//#define MASTER_ADDR "207.199.80.230"
#define FIRST_HB_TIME 10000 /* 10 sec */
#define HB_TIME 60000 /* 1 minute */
#define KA_TIME 20000 /* 20 sec */
#define MIN_STATECHANGED_HB_TIME 10000 /* 10 sec */
#define MAX_FIRST_COUNT 4 /* 4 tries */
#define MAX_DATA_SIZE 1400
#define INBUF_LEN 256
#define PUBLIC_ADDR_LEN 12
#define QR2_OPTION_USE_QUERY_CHALLENGE 128
#define PACKET_QUERY 0x00
#define PACKET_CHALLENGE 0x01
#define PACKET_ECHO 0x02
#define PACKET_ECHO_RESPONSE 0x05 // 0x05, not 0x03 (order)
#define PACKET_HEARTBEAT 0x03
#define PACKET_ADDERROR 0x04
#define PACKET_CLIENT_MESSAGE 0x06
#define PACKET_CLIENT_MESSAGE_ACK 0x07
#define PACKET_KEEPALIVE 0x08
#define PACKET_PREQUERY_IP_VERIFY 0x09
#define MAX_LOCAL_IP 5
//magic bytes for nat negotiation message
#define NATNEG_MAGIC_LEN 6
#define NN_MAGIC_0 0xFD
#define NN_MAGIC_1 0xFC
#define NN_MAGIC_2 0x1E
#define NN_MAGIC_3 0x66
#define NN_MAGIC_4 0x6A
#define NN_MAGIC_5 0xB2
// ex flags are the 11th byte in the query packet
// Old queries will end at 10 bytes.
#define QR2_EXFLAG_SPLIT (1<<0)
// Some other settings for split packet responses
#define QR2_SPLITNUM_MAX 7
#define QR2_SPLITNUM_FINALFLAG (1<<7)
/********
TYPEDEFS
********/
typedef unsigned char uchar;
struct qr2_keybuffer_s
{
uchar keys[MAX_REGISTERED_KEYS];
int numkeys;
};
struct qr2_buffer_s
{
char buffer[MAX_DATA_SIZE];
int len;
};
#define AVAILABLE_BUFFER_LEN(a) (MAX_DATA_SIZE - (a)->len)
/********
VARS
********/
struct qr2_implementation_s static_qr2_rec = {INVALID_SOCKET};
static qr2_t current_rec = &static_qr2_rec;
char qr2_hostname[64];
static int num_local_ips = 0;
static struct in_addr local_ip_list[MAX_LOCAL_IP];
/********
PROTOTYPES
********/
static void send_heartbeat(qr2_t qrec, int statechanged);
static void send_keepalive(qr2_t qrec);
static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent);
static void qr2_check_queries(qr2_t qrec);
static void qr2_check_send_heartbeat(qr2_t qrec);
static void enum_local_ips();
static void qr2_expire_ip_verify(qr2_t qrec);
qr2_error_t qr2_create_socket(/*[out]*/SOCKET *sock, const char *ip, /*[in/out]*/int * port);
/****************************************************************************/
/* PUBLIC FUNCTIONS */
/****************************************************************************/
/* qr2_init: Initializes the sockets, etc. Returns an error value
if an error occured, or 0 otherwise */
qr2_error_t qr2_init_socketA(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const char *gamename, const char *secret_key,
int ispublic, int natnegotiate,
qr2_serverkeycallback_t server_key_callback,
qr2_playerteamkeycallback_t player_key_callback,
qr2_playerteamkeycallback_t team_key_callback,
qr2_keylistcallback_t key_list_callback,
qr2_countcallback_t playerteam_count_callback,
qr2_adderrorcallback_t adderror_callback,
void *userdata)
{
char hostname[64];
int ret;
int i;
qr2_t cr;
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_init_socket()\r\n");
if (qrec == NULL)
{
cr = &static_qr2_rec;
}
else
{
*qrec = (qr2_t)gsimalloc(sizeof(struct qr2_implementation_s));
cr = *qrec;
}
srand((unsigned int)current_time());
strcpy(cr->gamename,gamename);
strcpy(cr->secret_key,secret_key);
cr->qport = boundport;
cr->lastheartbeat = 0;
cr->lastka = 0;
cr->hbsock = s;
cr->listed_state = 1;
cr->udata = userdata;
cr->server_key_callback = server_key_callback;
cr->player_key_callback = player_key_callback;
cr->team_key_callback = team_key_callback;
cr->key_list_callback = key_list_callback;
cr->playerteam_count_callback = playerteam_count_callback;
cr->adderror_callback = adderror_callback;
cr->nn_callback = NULL;
cr->cm_callback = NULL;
cr->cdkeyprocess = NULL;
cr->ispublic = ispublic;
cr->read_socket = 0;
cr->nat_negotiate = natnegotiate;
cr->publicip = 0;
cr->publicport = 0;
cr->pa_callback = NULL;
cr->cc_callback = NULL;
cr->userstatechangerequested = 0;
cr->backendoptions = 0;
for (i = 0 ; i < REQUEST_KEY_LEN ; i++)
cr->instance_key[i] = (char)(rand() % 0xFF);
for (i = 0 ; i < RECENT_CLIENT_MESSAGES_TO_TRACK ; i++)
cr->client_message_keys[i] = -1;
cr->cur_message_key = 0;
memset(cr->ipverify, 0, sizeof(cr->ipverify));
//if (num_local_ips == 0) - caching IPs can result in problems if DHCP has allocated a new one
enum_local_ips();
if (ispublic)
{
int override = qr2_hostname[0];
if(!override)
sprintf(hostname, "%s.master." GSI_DOMAIN_NAME, gamename);
ret = get_sockaddrin(override?qr2_hostname:hostname, MASTER_PORT, &(cr->hbaddr), NULL);
if (ret == 1)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"%s resolved to %s\r\n", override?qr2_hostname:hostname, inet_ntoa(cr->hbaddr.sin_addr));
}
else
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError,
"Failed on DNS lookup for %s \r\n", override?qr2_hostname:hostname);
}
}
else //don't need to look up
ret = 1;
if (!ret)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_HotError,
"qr2_init_socket() returned failed (DNS error)\r\n");
return e_qrdnserror;
}
else
{
return e_qrnoerror;
}
}
qr2_error_t qr2_init_socketW(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const unsigned short *gamename, const unsigned short *secret_key,
int ispublic, int natnegotiate,
qr2_serverkeycallback_t server_key_callback,
qr2_playerteamkeycallback_t player_key_callback,
qr2_playerteamkeycallback_t team_key_callback,
qr2_keylistcallback_t key_list_callback,
qr2_countcallback_t playerteam_count_callback,
qr2_adderrorcallback_t adderror_callback,
void *userdata)
{
char gamename_A[255];
char secretkey_A[255];
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_init_socketW()\r\n");
UCS2ToAsciiString(gamename, gamename_A);
UCS2ToAsciiString(secret_key, secretkey_A);
return qr2_init_socketA(qrec, s, boundport, gamename_A, secretkey_A, ispublic, natnegotiate,
server_key_callback, player_key_callback, team_key_callback,
key_list_callback, playerteam_count_callback, adderror_callback, userdata);
}
qr2_error_t qr2_create_socket(/*[out]*/SOCKET *sock, const char *ip, /*[in/out]*/int * port)
{
struct sockaddr_in saddr;
SOCKET hbsock;
int maxport;
int lasterror = 0;
int baseport = *port;
#if defined(_LINUX)
unsigned int saddrlen;
#else
int saddrlen;
#endif
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_create_socket()\r\n");
SocketStartUp();
hbsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (INVALID_SOCKET == hbsock)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError,
"Failed to create heartbeat socket\r\n");
return e_qrwsockerror;
}
maxport = baseport + NUM_PORTS_TO_TRY;
while (baseport < maxport)
{
get_sockaddrin(ip,baseport,&saddr,NULL);
if (saddr.sin_addr.s_addr == htonl(0x7F000001)) //localhost -- we don't want that!
saddr.sin_addr.s_addr = INADDR_ANY;
lasterror = bind(hbsock, (struct sockaddr *)&saddr, sizeof(saddr));
if (lasterror == 0)
break; //we found a port
baseport++;
}
if (lasterror != 0) //we weren't able to find a port
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError,
"Failed to bind() query socket\r\n");
return e_qrbinderror;
}
if (baseport == 0) //we bound it dynamically
{
saddrlen = sizeof(saddr);
lasterror = getsockname(hbsock,(struct sockaddr *)&saddr, &saddrlen);
if (lasterror)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError,
"Query socket bind() success, but getsockname() failed\r\n");
return e_qrbinderror;
}
baseport = ntohs(saddr.sin_port);
}
*sock = hbsock;
*port = baseport;
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"Query socket created and bound to port %d\r\n", *port);
return e_qrnoerror;
}
qr2_error_t qr2_initA(/*[out]*/qr2_t *qrec, const char *ip, int baseport, const char *gamename, const char *secret_key,
int ispublic, int natnegotiate,
qr2_serverkeycallback_t server_key_callback,
qr2_playerteamkeycallback_t player_key_callback,
qr2_playerteamkeycallback_t team_key_callback,
qr2_keylistcallback_t key_list_callback,
qr2_countcallback_t playerteam_count_callback,
qr2_adderrorcallback_t adderror_callback,
void *userdata)
{
SOCKET hbsock;
qr2_error_t ret;
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_init()\r\n");
ret = qr2_create_socket(&hbsock, ip, &baseport);
if(ret != e_qrnoerror)
{
SocketShutDown();
return ret;
}
ret = qr2_init_socketA(qrec, hbsock, baseport, gamename, secret_key, ispublic, natnegotiate, server_key_callback, player_key_callback, team_key_callback, key_list_callback, playerteam_count_callback, adderror_callback, userdata);
if (qrec == NULL)
qrec = &current_rec;
(*qrec)->read_socket = 1;
return ret;
}
qr2_error_t qr2_initW(/*[out]*/qr2_t *qrec, const unsigned short *ip, int baseport, const unsigned short *gamename, const unsigned short *secret_key,
int ispublic, int natnegotiate,
qr2_serverkeycallback_t server_key_callback,
qr2_playerteamkeycallback_t player_key_callback,
qr2_playerteamkeycallback_t team_key_callback,
qr2_keylistcallback_t key_list_callback,
qr2_countcallback_t playerteam_count_callback,
qr2_adderrorcallback_t adderror_callback,
void *userdata)
{
char ip_A[255];
char gamename_A[255];
char secretkey_A[255];
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_initW()\r\n");
if (ip != NULL) // NULL value is valid for IP
UCS2ToAsciiString(ip, ip_A);
UCS2ToAsciiString(gamename, gamename_A);
UCS2ToAsciiString(secret_key, secretkey_A);
return qr2_initA(qrec, (ip!=NULL)?ip_A:NULL, baseport, gamename_A, secretkey_A, ispublic, natnegotiate, server_key_callback, player_key_callback, team_key_callback, key_list_callback, playerteam_count_callback, adderror_callback, userdata);
}
void qr2_register_natneg_callback(qr2_t qrec, qr2_natnegcallback_t nncallback)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_register_natneg_callback()\r\n");
if (qrec == NULL)
qrec = current_rec;
qrec->nn_callback = nncallback;
}
void qr2_register_clientmessage_callback(qr2_t qrec, qr2_clientmessagecallback_t cmcallback)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_register_clientmessage_callback()\r\n");
if (qrec == NULL)
qrec = current_rec;
qrec->cm_callback = cmcallback;
}
void qr2_register_publicaddress_callback(qr2_t qrec, qr2_publicaddresscallback_t pacallback)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_register_publicaddress_callback()\r\n");
if (qrec == NULL)
qrec = current_rec;
qrec->pa_callback = pacallback;
}
void qr2_register_clientconnected_callback(qr2_t qrec, qr2_clientconnectedcallback_t cccallback)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_register_clientconnected_callback()\r\n");
if (qrec == NULL)
qrec = current_rec;
qrec->cc_callback = cccallback;
}
void qr2_register_denyresponsetoip_callback(qr2_t qrec, qr2_denyqr2responsetoipcallback_t dertoipcallback)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_register_denyresponsetoip_callback()\r\n");
if (qrec == NULL)
qrec = current_rec;
qrec->denyresp2_ip_callback = dertoipcallback;
}
/* qr2_think: Processes any waiting queries, and sends a
heartbeat if needed */
void qr2_think(qr2_t qrec)
{
if (qrec == NULL)
qrec = current_rec;
if (qrec->ispublic)
qr2_check_send_heartbeat(qrec);
qr2_check_queries(qrec);
qr2_expire_ip_verify(qrec);
NNThink();
}
/* qr2_check_queries: Processes any waiting queries */
void qr2_check_queries(qr2_t qrec)
{
static char indata[INBUF_LEN]; //256 byte input buffer
struct sockaddr_in saddr;
int error;
#if defined(_LINUX)
unsigned int saddrlen = sizeof(struct sockaddr_in);
#else
int saddrlen = sizeof(struct sockaddr_in);
#endif
if (!qrec->read_socket)
return; //not our job
while(CanReceiveOnSocket(qrec->hbsock))
{
//else we have data
error = (int)recvfrom(qrec->hbsock, indata, (INBUF_LEN - 1), 0, (struct sockaddr *)&saddr, &saddrlen);
if (gsiSocketIsNotError(error))
{
indata[error] = '\0';
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"Received %d bytes on query socket\r\n", error);
gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump,
indata, error);
qr2_parse_queryA(qrec, indata, error, (struct sockaddr *)&saddr);
}
else if (error == 0)
{
// socket closed?
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"CanReceiveOnSocket() returned true, but recvfrom return 0!\r\n", error);
}
else
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"CanReceiveOnSocket() returned true, but recvfrom failed!\r\n", error);
}
}
}
/* check_send_heartbeat: Perform any scheduled outgoing
heartbeats */
void qr2_check_send_heartbeat(qr2_t qrec)
{
gsi_time tc = current_time();
if (INVALID_SOCKET == qrec->hbsock)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
"HBSock is invalid\r\n");
return; //no sockets to work with!
}
//check if we need to send a heartbet
if (qrec->listed_state > 0 && tc - qrec->lastheartbeat > FIRST_HB_TIME)
{ //check to see if we haven't gotten a query yet
if (qrec->listed_state >= MAX_FIRST_COUNT)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError,
"No response from master, generating NoChallengeResponse error\r\n");
qrec->listed_state = 0; //we failed to get a challenge! let them know
#ifndef GSI_UNICODE
qrec->adderror_callback(e_qrnochallengeerror, "No challenge value was received from the master server.", qrec->udata);
#else
qrec->adderror_callback(e_qrnochallengeerror, L"No challenge value was received from the master server.", qrec->udata);
#endif
return;
} else
{
send_heartbeat(qrec, 3);
qrec->listed_state++;
}
}
else if (qrec->userstatechangerequested && (tc - qrec->lastheartbeat > MIN_STATECHANGED_HB_TIME))
send_heartbeat(qrec,1); // Send out pending statechange request
else if (tc - qrec->lastheartbeat > HB_TIME || qrec->lastheartbeat == 0 || tc < qrec->lastheartbeat)
send_heartbeat(qrec,0); // Send out a normal hearbeat
if (current_time() - qrec->lastka > KA_TIME) //send a keep alive (to keep NAT port mappings the same if possible)
send_keepalive(qrec);
}
/* qr2_send_statechanged: Sends a statechanged heartbeat, call when
your gamemode changes */
void qr2_send_statechanged(qr2_t qrec)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
"qr2_send_statechanged()\r\n");
if (qrec == NULL)
qrec = current_rec;
if (!qrec->ispublic)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Warning,
"Requested send statechange for LAN game, discarding\r\n");
return;
}
if (current_time() - qrec->lastheartbeat < MIN_STATECHANGED_HB_TIME)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice,
"Queing statechange for later send (too soon)\r\n");
// Queue up the statechange and send later
qrec->userstatechangerequested = 1;
return; // don't allow the server to spam statechanges
}
send_heartbeat(qrec, 1);
qrec->userstatechangerequested = 0; // clear the flag in case a queued statechange was still pending
}
/* qr2_shutdown: Cleans up the sockets and shuts down */
void qr2_shutdown(qr2_t qrec)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_StackTrace,
"qr2_shutdown()\r\n");
if (qrec == NULL)
qrec = current_rec;
if (qrec->ispublic)
send_heartbeat(qrec, 2);
if (INVALID_SOCKET != qrec->hbsock && qrec->read_socket) //if we own the socket
{
closesocket(qrec->hbsock);
}
qrec->hbsock = INVALID_SOCKET;
qrec->lastheartbeat = 0;
if(qrec->read_socket) //if we own the socket
{
SocketShutDown();
}
if (qrec != &static_qr2_rec) //need to gsifree it, it was dynamically allocated
{
gsifree(qrec);
}
// free ACE negotiate list
NNFreeNegotiateList(); //comment out if ACE is not being used
// BD: Removed - Peer SDK repeatedly calls qr2_shutdown, but
// keys should only be deallocated once.
// Developers should call this manually (when in GSI_UNICODE mode)
// qr2_internal_key_list_free();
}
gsi_bool qr2_keybuffer_add(qr2_keybuffer_t keybuffer, int keyid)
{
// mj these are codetime not runtime errors, changing to assert
if (keybuffer->numkeys >= MAX_REGISTERED_KEYS)
return gsi_false;
if (keyid < 1 || keyid > MAX_REGISTERED_KEYS)
return gsi_false;
keybuffer->keys[keybuffer->numkeys++] = (uchar)keyid;
return gsi_true;
}
gsi_bool qr2_buffer_add_int(qr2_buffer_t outbuf, int value)
{
char temp[20];
sprintf(temp, "%d", value);
return qr2_buffer_addA(outbuf, temp);
}
gsi_bool qr2_buffer_addA(qr2_buffer_t outbuf, const char *value)
{
GS_ASSERT(outbuf)
GS_ASSERT(value)
{
int copylen;
copylen = (int)strlen(value) + 1;
if (copylen > AVAILABLE_BUFFER_LEN(outbuf))
copylen = AVAILABLE_BUFFER_LEN(outbuf); //max length we can fit in the buffer
if (copylen <= 0)
return gsi_false; //no space
memcpy(outbuf->buffer + outbuf->len, value, (unsigned int)copylen);
outbuf->len += copylen;
outbuf->buffer[outbuf->len - 1] = 0; //make sure it's null terminated
return gsi_true;
}
}
#if defined(GSI_UNICODE)
gsi_bool qr2_buffer_addW(qr2_buffer_t outbuf, const unsigned short *value)
{
char value_A[4096];
UCS2ToUTF8String(value, value_A);
return qr2_buffer_addA(outbuf, value_A);
}
#endif
static void enum_local_ips()
{
struct hostent *phost;
phost = getlocalhost();
if (phost == NULL)
return;
for (num_local_ips = 0 ; num_local_ips < MAX_LOCAL_IP ; num_local_ips++)
{
if (phost->h_addr_list[num_local_ips] == 0)
break;
memcpy(&local_ip_list[num_local_ips], phost->h_addr_list[num_local_ips], sizeof(struct in_addr));
}
}
/****************************************************************************/
/* Return a sockaddrin for the given host (numeric or DNS) and port)
Returns the hostent in savehent if it is not NULL */
static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent)
{
struct hostent *hent = NULL;
saddr->sin_family = AF_INET;
saddr->sin_port = htons((unsigned short)port);
if (host == NULL)
saddr->sin_addr.s_addr = INADDR_ANY;
else
saddr->sin_addr.s_addr = inet_addr(host);
if (saddr->sin_addr.s_addr == INADDR_NONE && strcmp(host,"255.255.255.255") != 0)
{
hent = gethostbyname(host);
if (!hent)
return 0;
saddr->sin_addr.s_addr = *(unsigned int *)hent->h_addr_list[0];
}
if (savehent != NULL)
*savehent = hent;
return 1;
}
/*****************************************************************************/
/* Various encryption / encoding routines */
static void swap_byte ( uchar *a, uchar *b )
{
uchar swapByte;
swapByte = *a;
*a = *b;
*b = swapByte;
}
static uchar encode_ct ( uchar c )
{
if (c < 26) return (uchar)('A'+c);
if (c < 52) return (uchar)('a'+c-26);
if (c < 62) return (uchar)('0'+c-52);
if (c == 62) return (uchar)('+');
if (c == 63) return (uchar)('/');
return 0;
}
static void gs_encode ( uchar *ins, int size, uchar *result )
{
int i,pos;
uchar trip[3];
uchar kwart[4];
i=0;
while (i < size)
{
for (pos=0 ; pos <= 2 ; pos++, i++)
if (i < size) trip[pos] = *ins++;
else trip[pos] = '\0';
kwart[0] = (unsigned char)( (trip[0]) >> 2);
kwart[1] = (unsigned char)((((trip[0]) & 3) << 4) + ((trip[1]) >> 4));
kwart[2] = (unsigned char)((((trip[1]) & 15) << 2) + ((trip[2]) >> 6));
kwart[3] = (unsigned char)( (trip[2]) & 63);
for (pos=0; pos <= 3; pos++) *result++ = encode_ct(kwart[pos]);
}
*result='\0';
}
static void gs_encrypt ( uchar *key, int key_len, uchar *buffer_ptr, int buffer_len )
{
int counter;
uchar x, y, xorIndex;
uchar state[256];
for ( counter = 0; counter < 256; counter++) state[counter] = (uchar) counter;
x = 0; y = 0;
for ( counter = 0; counter < 256; counter++)
{
y = (uchar)((key[x] + state[counter] + y) % 256);
x = (uchar)((x + 1) % key_len);
swap_byte ( &state[counter], &state[y] );
}
x = 0; y = 0;
for ( counter = 0; counter < buffer_len; counter ++)
{
x = (uchar)((x + buffer_ptr[counter] + 1) % 256);
y = (uchar)((state[x] + y) % 256);
swap_byte ( &state[x], &state[y] );
xorIndex = (uchar)((state[x] + state[y]) % 256);
buffer_ptr[counter] ^= state[xorIndex];
}
}
/*****************************************************************************/
/* NAT Negotiation support */
static void NatNegProgressCallback(NegotiateState state, void *userdata)
{
// we don't do anything here
GSI_UNUSED(state);
GSI_UNUSED(userdata);
}
static void NatNegCompletedCallback(NegotiateResult result, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata)
{
qr2_t qrec = (qr2_t)userdata;
if(qrec->cc_callback)
{
if(result == nr_success)
{
qrec->cc_callback(gamesocket, remoteaddr, qrec->udata);
}
}
}
/*****************************************************************************/
static void qr_add_packet_header(qr2_buffer_t buf, char ptype, char *reqkey)
{
buf->buffer[0] = ptype;
memcpy(buf->buffer + 1, reqkey, REQUEST_KEY_LEN);
buf->len = REQUEST_KEY_LEN + 1;
}
#define MAX_CHALLENGE 64
static void compute_challenge_response(qr2_t qrec, qr2_buffer_t buf, char *challenge, int challengelen)
{
char encrypted_val[MAX_CHALLENGE + 1]; //don't need to null terminate
if(challengelen < 1)
return; // invalid, need room for the NUL
if (challengelen > (MAX_CHALLENGE + 1))
return; //invalid
if (challenge[challengelen - 1] != 0)
return; //invalid - must be NTS
strcpy(encrypted_val, challenge);
gs_encrypt((uchar *)qrec->secret_key, (int)strlen(qrec->secret_key), (uchar *)encrypted_val, challengelen - 1);
gs_encode((uchar *)encrypted_val,challengelen - 1, (uchar *)(buf->buffer + buf->len));
buf->len += (int)strlen(buf->buffer + buf->len) + 1;
}
static void handle_public_address(qr2_t qrec, char * buffer)
{
unsigned int ip;
unsigned int portTemp;
unsigned short port;
// get the public ip and port as the master server sees it
sscanf(buffer, "%08X%04X", &ip, &portTemp);
port = (unsigned short)portTemp;
ip = htonl(ip);
// sanity check
if((ip == 0) || (port == 0))
return;
#ifdef GSI_COMMON_DEBUG
{
IN_ADDR addr;
addr.s_addr = ip;
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
"Received public address (%s:%d)\r\n", inet_ntoa(addr), port);
}
#endif
// has anything changed?
if((qrec->publicip != ip) || (qrec->publicport != port))
{
qrec->publicip = ip;
qrec->publicport = port;
qrec->pa_callback(ip, port, qrec->udata);
}
}
static void qr_build_partial_query_reply(qr2_t qrec, qr2_buffer_t buf, qr2_key_type keytype, int keycount, uchar *keys)
{
struct qr2_keybuffer_s kb;
int playerteamcount;
unsigned short cttemp;
int i;
int pindex;
const char *k;
int len;
kb.numkeys = 0;
if (keycount == 0)
return; //no keys wanted
if (keytype == key_player || keytype == key_team) //need to add the player/team counts
{
if (AVAILABLE_BUFFER_LEN(buf) < sizeof(cttemp))
return; //no more space
playerteamcount = qrec->playerteam_count_callback(keytype, qrec->udata);
cttemp = htons((unsigned short)playerteamcount);
memcpy(buf->buffer + buf->len, &cttemp, sizeof(cttemp));
buf->len += sizeof(cttemp);
} else
playerteamcount = 1;
if (keycount == 0xFF) //need to get the list of keys
{
qrec->key_list_callback(keytype, &kb, qrec->udata);
//add all the keys
for (i = 0 ; i < kb.numkeys ; i++)
{
k = qr2_registered_key_list[kb.keys[i]];
if (k == NULL)
k = "unknown";
qr2_buffer_addA(buf, k);
if (keytype == key_server) //add the server values
{
len = buf->len;
qrec->server_key_callback(kb.keys[i], buf, qrec->udata);
if(len == buf->len)
qr2_buffer_addA(buf, "");
}
}
//add an extra null
if (AVAILABLE_BUFFER_LEN(buf) < 1)
return; //no space
buf->buffer[buf->len++] = 0;
keycount = kb.numkeys;
keys = kb.keys;
if (keytype == key_server)
return; //already added the keys
}
for (pindex = 0 ; pindex < playerteamcount ; pindex++)
{
for (i = 0 ; i < keycount ; i++)
{
len = buf->len;
if (keytype == key_server) //add the server keys
qrec->server_key_callback(keys[i], buf, qrec->udata);
else if (keytype == key_player)
qrec->player_key_callback(keys[i], pindex, buf, qrec->udata);
else if (keytype == key_team)
qrec->team_key_callback(keys[i], pindex, buf, qrec->udata);
if(len == buf->len)
qr2_buffer_addA(buf, "");
}
}
}
static void qr_build_query_reply(qr2_t qrec, qr2_buffer_t buf, int serverkeycount, uchar *serverkeys, int playerkeycount, uchar *playerkeys, int teamkeycount, uchar *teamkeys)
{
qr_build_partial_query_reply(qrec, buf, key_server, serverkeycount, serverkeys);
qr_build_partial_query_reply(qrec, buf, key_player, playerkeycount, playerkeys);
qr_build_partial_query_reply(qrec, buf, key_team, teamkeycount, teamkeys);
}
struct QRSplitQueryProgress
{
qr2_key_type mCurKeyType;
int mCurPacketNum;
int mCurKeyIndex; // serverkey index, playerkey index, teamkey index
int mCurSubCount; // number of players or number of teams
int mCurSubIndex; // current player num or current team num
struct qr2_keybuffer_s mKeyBuffer; // keybuffer, for key name indexing
};
// return values:
// gsi_true = send buffer, then call this function again
// gsi_false = don't send buffer, don't call this function again
static gsi_bool qr_build_split_query_reply(qr2_t qrec, qr2_buffer_t buf, struct QRSplitQueryProgress* progress)
{
unsigned char* packetNumPos = NULL; // Used to store the byte position of the packet number
// Make sure the key type is valid
// (The key type is set to invalid when all keys have been processed.)
//if (progress->mCurKeyType < 0 ||progress->mCurKeyType >= key_type_count)
if (progress->mCurKeyType >= key_type_count)
return gsi_false; // stop processing
// check buffer space
// (buffer should only contain header at this point)
if (AVAILABLE_BUFFER_LEN(buf) < 32)
return gsi_false; // no space?
// Dump the split packet "header"
qr2_buffer_addA(buf, "splitnum");
packetNumPos = (unsigned char*)&buf->buffer[buf->len++];
*packetNumPos = (gsi_u8)progress->mCurPacketNum++;
// Resume dumping at key_type level
while (progress->mCurKeyType < key_type_count)
{
// Get the list of keys if we don't have it already
if (progress->mKeyBuffer.numkeys == 0)
qrec->key_list_callback(progress->mCurKeyType, &progress->mKeyBuffer, qrec->udata);
// Get the list of players/teams if we don't have it already
if (progress->mCurSubCount == 0 && progress->mCurKeyType != key_server)
progress->mCurSubCount = qrec->playerteam_count_callback(progress->mCurKeyType, qrec->udata);
// check buffer space
if (AVAILABLE_BUFFER_LEN(buf) < 100)
return gsi_true; //no space
// Write the key type
buf->buffer[buf->len++] = (char)progress->mCurKeyType;
// For each key
while(progress->mCurKeyIndex < progress->mKeyBuffer.numkeys)
{
// check buffer space
int aRegisteredKeyIndex = progress->mKeyBuffer.keys[progress->mCurKeyIndex];
const char* aKeyName = qr2_registered_key_list[aRegisteredKeyIndex];
// Write the key name
if (gsi_is_false( qr2_buffer_addA(buf,aKeyName) ))
return gsi_true; // send, then try again
if (progress->mCurKeyType == key_server)
{
// write the key value
qrec->server_key_callback(aRegisteredKeyIndex, buf, qrec->udata);
// make sure the key was written
if (AVAILABLE_BUFFER_LEN(buf) < 1)
return gsi_true; //ran out of space! retry this key/value next packet
}
else
{
if (AVAILABLE_BUFFER_LEN(buf) < 1)
return gsi_true; //ran out of space, retry this key/value next packet
// Non-split packets implicitly being with player/team number zero,
// split packet explicitly specify the starting number
buf->buffer[buf->len++] = (char)progress->mCurSubIndex;
// For each player/team
while(progress->mCurSubIndex < progress->mCurSubCount)
{
// dump the value into the buffer
if (progress->mCurKeyType == key_player)
qrec->player_key_callback(aRegisteredKeyIndex, progress->mCurSubIndex, buf, qrec->udata);
else if (progress->mCurKeyType == key_team)
qrec->team_key_callback(aRegisteredKeyIndex, progress->mCurSubIndex, buf, qrec->udata);
// make sure the key was written
if (AVAILABLE_BUFFER_LEN(buf) < 1)
return gsi_true; //ran out of space, try again next packet
// move onto the next player/team value
progress->mCurSubIndex++;
}
// append a null to signify end of this team/player key
if (AVAILABLE_BUFFER_LEN(buf) > 0)
buf->buffer[buf->len++] = '\0';
}
// move onto next key
progress->mCurKeyIndex++;
progress->mCurSubIndex = 0;
}
// append a null to signify end of this key_type section
if (AVAILABLE_BUFFER_LEN(buf) > 0)
buf->buffer[buf->len++] = '\0';
// Move onto next key type
progress->mCurKeyType = (qr2_key_type)(progress->mCurKeyType + 1);
progress->mCurKeyIndex = 0;
progress->mCurSubCount = 0;
progress->mCurSubIndex = 0;
progress->mKeyBuffer.numkeys = 0;
}
// Add the "final" flag to the packet number
*packetNumPos |= QR2_SPLITNUM_FINALFLAG;
return gsi_true; // function will bail without sending next iteration
}
static void qr_process_query(qr2_t qrec, qr2_buffer_t buf, uchar *qdata, int len, struct sockaddr* sender)
{
uchar serverkeycount;
uchar playerkeycount;
uchar teamkeycount;
uchar exflags = 0;
uchar *serverkeys = NULL;
uchar *playerkeys = NULL;
uchar *teamkeys = NULL;
if (len < 3)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
"Discarding invalid query (too short#1: %d bytes total)\r\n", len);
return; //invalid
}
serverkeycount = qdata[0];
qdata++;
len--;
if (serverkeycount != 0 && serverkeycount != 0xFF)
{
serverkeys = qdata;
qdata += serverkeycount;
len -= serverkeycount;
}
if (len < 2)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
"Discarding invalid query (too short#2: %d bytes remain)\r\n", len);
return; //invalid
}
playerkeycount = qdata[0];
qdata++;
len--;
if (playerkeycount != 0 && playerkeycount != 0xFF)
{
playerkeys = qdata;
qdata += playerkeycount;
len -= playerkeycount;
}
if (len < 1)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
"Discarding invalid query (too short#3: %d bytes remain)\r\n", len);
return; //invalid
}
teamkeycount = qdata[0];
qdata++;
len--;
if (teamkeycount != 0 && teamkeycount != 0xFF)
{
teamkeys = qdata;
qdata += teamkeycount;
len -= teamkeycount;
}
if (len < 0)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
"Discarding invalid query (too short#4: %d bytes remain)\r\n", len);
return; //invalid
}
// check the exflags
if (len > 0)
{
exflags = qdata[0];
len--;
}
// Support split queries?
if ((exflags & QR2_EXFLAG_SPLIT)==QR2_EXFLAG_SPLIT)
{
struct QRSplitQueryProgress progress;
progress.mCurPacketNum = 0;
progress.mCurKeyType = key_server;
progress.mCurKeyIndex = 0;
progress.mCurSubCount = 0;
progress.mCurSubIndex = 0;
progress.mKeyBuffer.numkeys = 0;
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"Building query reply (split packet supported)\r\n");
// Send packets as long as we need to
while (gsi_true == qr_build_split_query_reply(qrec, buf, &progress))
{
sendto(qrec->hbsock, buf->buffer, buf->len, 0, sender, sizeof(struct sockaddr_in));
buf->len = 5; // reset buffer but preserve 5-byte qr2 header
if (progress.mCurPacketNum > QR2_SPLITNUM_MAX)
return; // more than 7 isn't supported (likely a bug if you hit it)
}
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"Finished split query reply (%d packets)\r\n", progress.mCurPacketNum);
}
else
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"Building query reply (single packet)\r\n");
qr_build_query_reply(qrec, buf, serverkeycount, serverkeys, playerkeycount, playerkeys, teamkeycount, teamkeys);
sendto(qrec->hbsock, buf->buffer, buf->len, 0, sender, sizeof(struct sockaddr_in));
}
GSI_UNUSED(sender);
}
/*
static void qr_build_partial_old_query_reply(qr2_t qrec, qr2_buffer_t buf, qr2_key_type keytype)
{
char tempkeyname[128];
struct qr2_keybuffer_s kb;
int playerteamcount;
int i;
int pindex;
const char *k;
int len;
kb.numkeys = 0;
if (keytype == key_player || keytype == key_team) //need to add the player/team counts
{
playerteamcount = qrec->playerteam_count_callback(keytype, qrec->udata);
} else
playerteamcount = 1;
qrec->key_list_callback(keytype, &kb, qrec->udata);
//add all the keys
for (i = 0 ; i < kb.numkeys ; i++)
{
k = qr2_registered_key_list[kb.keys[i]];
if (k == NULL)
k = "unknown";
if (keytype == key_server) //add the server values
{
qr2_buffer_addA(buf, k);
buf->buffer[buf->len - 1] = '\\';
len = buf->len;
qrec->server_key_callback(kb.keys[i], buf, qrec->udata);
if(len == buf->len)
qr2_buffer_addA(buf, "");
buf->buffer[buf->len - 1] = '\\';
} else //need to look it up for each player/team
{
for (pindex = 0 ; pindex < playerteamcount ; pindex++)
{
sprintf(tempkeyname, "%s%d", k, pindex);
qr2_buffer_addA(buf, tempkeyname);
buf->buffer[buf->len - 1] = '\\';
len = buf->len;
if (keytype == key_player)
qrec->player_key_callback(kb.keys[i], pindex, buf, qrec->udata);
else if (keytype == key_team)
qrec->team_key_callback(kb.keys[i], pindex, buf, qrec->udata);
if(len == buf->len)
qr2_buffer_addA(buf, "");
buf->buffer[buf->len - 1] = '\\';
}
}
}
}
*/
//we just build a status reply, since we don't have equivalent callbacks
/*static void qr_process_old_query(qr2_t qrec, qr2_buffer_t buf)
{
buf->len = 1;
buf->buffer[0] = '\\';
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Comment,
"Processing QR1 style query\r\n");
qr_build_partial_old_query_reply(qrec, buf, key_server);
qr_build_partial_old_query_reply(qrec, buf, key_player);
qr_build_partial_old_query_reply(qrec, buf, key_team);
qr2_buffer_addA(buf, "final\\\\queryid\\1.1");
buf->len--; //remove the final null;
}*/
static void qr_process_client_message(qr2_t qrec, char *buf, int len)
{
unsigned char natNegBytes[NATNEG_MAGIC_LEN] = {NN_MAGIC_0,NN_MAGIC_1,NN_MAGIC_2,NN_MAGIC_3,NN_MAGIC_4,NN_MAGIC_5};
int i;
int isnatneg = 1;
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice,
"Processing client message\r\n");
//see if it's a natneg request..
if (len >= NATNEG_MAGIC_LEN + 4)
{
for (i = 0 ; i < NATNEG_MAGIC_LEN ; i++)
if ((unsigned char)buf[i] != natNegBytes[i])
{
isnatneg = 0;
break;
}
} else
isnatneg = 0;
if (isnatneg)
{
int cookie;
memcpy(&cookie, buf + NATNEG_MAGIC_LEN, 4);
cookie = (int)ntohl((unsigned int)cookie);
// check if we should do natneg
if(qrec->cc_callback)
{
NegotiateError error;
if(__GSIACResult != GSIACAvailable)
{
// fake that we passed the availability check
__GSIACResult = GSIACAvailable;
strcpy(__GSIACGamename, qrec->gamename);
}
// do the negotiation
error = NNBeginNegotiationWithSocket(qrec->hbsock, cookie, 1, NatNegProgressCallback, NatNegCompletedCallback, qrec);
if(error != ne_noerror)
{
// we can ignore errors
}
}
else if (qrec->nn_callback)
{
qrec->nn_callback(cookie, qrec->udata);
}
} else
if (qrec->cm_callback)
{
#ifndef GSI_UNICODE
qrec->cm_callback(buf, len, qrec->udata);
#else
unsigned short* buf_W;
buf_W = UTF8ToUCS2StringAlloc(buf);
qrec->cm_callback(buf_W, wcslen(buf_W), qrec->udata);
#endif
}
}
static int qr_got_recent_message(qr2_t qrec, int msgkey)
{
int i;
for (i = 0 ; i < RECENT_CLIENT_MESSAGES_TO_TRACK ; i++)
{
if (qrec->client_message_keys[i] == msgkey)
return 1;
}
//else, add it to the list
qrec->cur_message_key = (qrec->cur_message_key + 1) % RECENT_CLIENT_MESSAGES_TO_TRACK;
qrec->client_message_keys[qrec->cur_message_key] = msgkey;
return 0;
}
// Send a random value to the user, to verify IP address
static gsi_bool qr2_process_ip_verify(qr2_t qrec, struct qr2_buffer_s* buf, struct sockaddr_in* sender)
{
int i=0;
gsi_time now = current_time();
int firstFreeIndex = -1;
int numDuplicates = 0;
// if the query challenge is disabled, return 0 as the challenge
if ((qrec->backendoptions & QR2_OPTION_USE_QUERY_CHALLENGE) == 0)
{
qr2_buffer_add_int(buf, 0);
return gsi_true;
}
// check if this ip/port combo is already in the list
for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++)
{
// Mark the first free index when/if found
if (firstFreeIndex == -1 && qrec->ipverify[i].addr.sin_addr.s_addr == 0)
firstFreeIndex = i;
// Count any indexes that match this IP/port
if (qrec->ipverify[i].addr.sin_addr.s_addr == sender->sin_addr.s_addr &&
qrec->ipverify[i].addr.sin_port == sender->sin_port)
{
numDuplicates++;
}
}
// discard if too many duplicates or no index found
if (numDuplicates > QR2_IPVERIFY_MAXDUPLICATES)
return gsi_false;
if (firstFreeIndex == -1)
return gsi_false; // no free indexes
// create a random challenge for this ip/port combo
qrec->ipverify[firstFreeIndex].addr = *sender;
qrec->ipverify[firstFreeIndex].challenge = htonl( (rand() << 16) | rand() );
qrec->ipverify[firstFreeIndex].createtime = now;
qr2_buffer_add_int(buf, (int)qrec->ipverify[firstFreeIndex].challenge);
return gsi_true; // buffer ready to be sent
}
// Check if the returned ipverify value matches the random we sent earlier
// If it matches, remove it
static gsi_bool qr2_check_ip_verify(qr2_t qrec, struct sockaddr_in* sender, gsi_u32 ipverify)
{
int i=0;
for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++)
{
if (qrec->ipverify[i].addr.sin_addr.s_addr == sender->sin_addr.s_addr &&
qrec->ipverify[i].addr.sin_port == sender->sin_port)
{
if (qrec->ipverify[i].challenge == ipverify)
{
// reset structure
qrec->ipverify[i].addr.sin_addr.s_addr = 0;
qrec->ipverify[i].addr.sin_port = 0;
return gsi_true;
}
//else
// keep searching...a single IP may have multiple outstanding challenges.
}
}
return gsi_false;
}
// Expire old verify attempts
static void qr2_expire_ip_verify(qr2_t qrec)
{
int i=0;
gsi_time now = current_time();
for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++)
{
if (qrec->ipverify[i].addr.sin_addr.s_addr != 0 && (now - qrec->ipverify[i].createtime > QR2_IPVERIFY_TIMEOUT))
qrec->ipverify[i].addr.sin_addr.s_addr = 0;
}
}
/* parse_query: parse an incoming query and reply to each query */
void qr2_parse_queryA(qr2_t qrec, char *query, int len, struct sockaddr *sender)
{
struct qr2_buffer_s buf;
char ptype;
char *reqkey;
char *pos;
gsi_u32 ipverify = 0;
int i;
buf.len = 0;
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_StackTrace,
"qr2_parse_queryA()\r\n");
if (qrec == NULL)
qrec = current_rec;
if (query[0] == 0x3B) /* a cdkey query */
{
if (qrec->cdkeyprocess != NULL)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
"Forwarding cdkey query onto cdkey sdk\r\n");
qrec->cdkeyprocess(query, len, sender);
}
else
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
"Received cdkey query but not using qr2-cdkey integration!\r\n");
}
return;
}
/*if (query[0] == '\\') //it's a QR1-style query
{
qr_process_old_query(qrec, &buf);
sendto(qrec->hbsock, buf.buffer, buf.len, 0, sender, sizeof(struct sockaddr_in));
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"Sent %d bytes as QR1 query response\r\n", buf.len);
gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump,
buf.buffer, buf.len);
return;
}*/
if((len >= 6) && (memcmp(query, NNMagicData, NATNEG_MAGIC_LEN) == 0)) /* a natneg query */
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
"Forwarding natneg query onto natneg sdk\r\n");
NNProcessData(query, len, (struct sockaddr_in*)sender);
return;
}
if (len < 7)
return; //too small to be valid
//check the magic...
if ((uchar)query[0] != QR_MAGIC_1 || (uchar)query[1] != QR_MAGIC_2)
return;
//#define SIMULATE_HARD_FIREWALL
#if defined(SIMULATE_HARD_FIREWALL)
// ignore all QR2 packets on this port
// generates "no challenge" error
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Warning,
"SIMULATE_HARD_FIREWALL defined, ignoring QR2 packet\r\n");
return;
#endif
//#define SIMULATE_FIREWALL
#if defined(SIMULATE_FIREWALL)
// ignore messages that are not from the gamespy master
{
unsigned int aMasterAddr = (cr->hbaddr.sin_addr);
if (((SOCKADDR_IN*)sender)->sin_addr.s_addr != aMasterAddr)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Warning,
"SIMULATE_FIREWALL defined, ignoring non-master QR2 packet\r\n");
return;
}
}
#endif
//#if defined(QR2_IP_FILTER)
if (qrec->denyresp2_ip_callback)
{
unsigned int aMasterAddr = (qrec->hbaddr.sin_addr.s_addr);
unsigned int senderAddr = ((SOCKADDR_IN*)sender)->sin_addr.s_addr; //in hbo
if ((aMasterAddr & 0xffff) != (senderAddr & 0xffff)) //if sender ip is not in gamespy subnet /16 (fake)
{
int deny_result = 0;
qrec->denyresp2_ip_callback(qrec->udata, senderAddr, &deny_result);
if (deny_result)
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Warning,
"QR2_IP_FILTER defined, ignoring QR2 packet from denied ip\r\n");
return;
}
}
}
//#endif //#if defined(QR2_IP_FILTER)
if (qrec->listed_state > 0)
qrec->listed_state = 0;
ptype = query[2];
reqkey = &query[3];
pos = query + 7;
len -= 7;
qr_add_packet_header(&buf, ptype, reqkey);
switch (ptype)
{
case PACKET_PREQUERY_IP_VERIFY:
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
"Received IP verify challenge request\r\n");
if (gsi_is_true(qr2_process_ip_verify(qrec, &buf, (struct sockaddr_in*)sender)))
break; // break so that we send below
else
return; // otherwise return and discard buf
case PACKET_QUERY:
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
"Processing query packet\r\n");
// When using query challenge option, verify that the client sent a PREQUERY_IP_VERIFY
if ((qrec->backendoptions & QR2_OPTION_USE_QUERY_CHALLENGE) == QR2_OPTION_USE_QUERY_CHALLENGE)
{
if (len < 4)
return; // too small for an ip-verify query
ipverify = ntohl(*(gsi_u32*)pos);
pos += 4;
len -= 4;
// Has this client verified their IP? (prevent IP spoofing)
if (gsi_is_false(qr2_check_ip_verify(qrec, (struct sockaddr_in*)sender, ipverify)))
{
// Don't send an error. As nice as the debug info would be,
// the incompatible SBs will interpret it as a server response.
return;
}
}
// qr_process_query now sends packets
qr_process_query(qrec, &buf, (uchar *)pos, len, sender);
return;
case PACKET_CHALLENGE:
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
"Processing challenge packet\r\n");
// Check the instance key (to prove this packet came from the master
for (i = 0 ; i < REQUEST_KEY_LEN ; i++)
{
if (reqkey[i] != qrec->instance_key[i])
return; //not a valid instance key
}
//calculate the challenge
if (len >= (PUBLIC_ADDR_LEN + 3))
{
unsigned int backendoptions;
// read options, then public address
sscanf(pos + len - (PUBLIC_ADDR_LEN + 3), "%02x", &backendoptions);
qrec->backendoptions = (gsi_u8)backendoptions;
#ifdef QR2_DEBUG_FORCE_USE_QUERY_CHALLENGE
qrec->backendoptions = QR2_OPTION_USE_QUERY_CHALLENGE;
#endif
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice,
"Received setting options: %d\r\n", qrec->backendoptions);
if(qrec->pa_callback)
handle_public_address(qrec, pos + len - (PUBLIC_ADDR_LEN + 1));
else
{
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
"Discarding public address (no callback set)\r\n");
}
}
compute_challenge_response(qrec, &buf, pos, len);
break;
case PACKET_ECHO:
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
"Processing echo packet\r\n");
//now add the echo data
if (len > 32)
len = 32; //max 32 bytes
buf.buffer[0] = PACKET_ECHO_RESPONSE;
memcpy(buf.buffer + buf.len, pos, (size_t)len);
buf.len += len;
break;
case PACKET_ADDERROR:
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
"Processing adderror packet\r\n");
if (qrec->listed_state == -1)
return; //we already got an error message
//verify the instance code
for (i = 0 ; i < REQUEST_KEY_LEN ; i++)
{
if (reqkey[i] != qrec->instance_key[i])
return; //not a valid instance key
}
if (len < 2)
return; //not a valid message
qrec->listed_state = -1;
#ifndef GSI_UNICODE
qrec->adderror_callback((qr2_error_t)*pos, pos + 1, qrec->udata);
#else
{
unsigned short* message_W;
message_W = UTF8ToUCS2StringAlloc(pos+1);
qrec->adderror_callback((qr2_error_t)*pos, message_W, qrec->udata);
gsifree(message_W);
}
#endif
return; //we don't need to send anything back for this type of message
case PACKET_CLIENT_MESSAGE:
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
"Processing clientmessage packet\r\n");
//verify the instance code
for (i = 0 ; i < REQUEST_KEY_LEN ; i++)
{
if (reqkey[i] != qrec->instance_key[i])
return; //not a valid instance key
}
if (len < 4) //no message key?
return;
buf.buffer[0] = PACKET_CLIENT_MESSAGE_ACK;
//add the msg key
memcpy(buf.buffer + buf.len, pos, (size_t)4);
buf.len += 4;
//see if we've recently gotten this same message, to help avoid dupes
memcpy(&i, pos, (size_t)4);
if (!qr_got_recent_message(qrec, i))
qr_process_client_message(qrec, pos + 4, len - 4);
//send an ack response
break;
case PACKET_KEEPALIVE:
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"Processing keepalive packet\r\n");
return; //if we get a keep alive, ignore it and return (just used to tell us the server knows about us)
default:
return; //not valid type
}
//send the reply
sendto(qrec->hbsock, buf.buffer, buf.len, 0, sender, sizeof(struct sockaddr_in));
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"Sent %d bytes as QR2 query response\r\n", buf.len);
gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump,
buf.buffer, buf.len);
}
/* NOT necessary since qr2_parse_query uses char not unsigned short
#if defined(GSI_UNICODE)
void qr2_parse_queryW(qr2_t qrec, unsigned short *query, int len, struct sockaddr *sender)
{
char query_A[4096];
UCS2ToUTF8String(query, query_A);
qr2_parse_queryA(qrec, query_A, len, sender);
}
#endif*/
/* send_keepalive: Send a keepalive packet to the hbmaster3 */
static void send_keepalive(qr2_t qrec)
{
struct qr2_buffer_s buf;
buf.len = 0;
qr_add_packet_header(&buf, PACKET_KEEPALIVE, qrec->instance_key);
sendto(qrec->hbsock, buf.buffer, buf.len, 0, (struct sockaddr *)&(qrec->hbaddr), sizeof(struct sockaddr_in));
//set the ka time to now
qrec->lastka = current_time();
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"Sent keepalive to master\r\n", buf);
}
/* send_heartbeat: Sends a heartbeat to the gamemaster,
adds \statechanged\ if statechanged != 0 */
static void send_heartbeat(qr2_t qrec, int statechanged)
{
struct qr2_buffer_s buf;
//int ret;
int i;
char ipkey[20];
buf.len = 0;
qr_add_packet_header(&buf, PACKET_HEARTBEAT, qrec->instance_key);
//now we add our special keys
for (i = 0 ; i < num_local_ips ; i++)
{
sprintf(ipkey, "localip%d", i);
qr2_buffer_addA(&buf, ipkey);
qr2_buffer_addA(&buf, inet_ntoa(local_ip_list[i]));
}
qr2_buffer_addA(&buf, "localport");
qr2_buffer_add_int(&buf, qrec->qport);
qr2_buffer_addA(&buf, "natneg");
qr2_buffer_addA(&buf, qrec->nat_negotiate ? "1" : "0");
if(statechanged)
{
qr2_buffer_addA(&buf, "statechanged");
qr2_buffer_add_int(&buf, statechanged);
}
qr2_buffer_addA(&buf, "gamename");
qr2_buffer_addA(&buf, qrec->gamename);
if(qrec->pa_callback)
{
qr2_buffer_addA(&buf, "publicip");
qr2_buffer_add_int(&buf, (int)qrec->publicip);
qr2_buffer_addA(&buf, "publicport");
qr2_buffer_add_int(&buf, qrec->publicport);
}
//add the rest of our keys
if (statechanged != 2) //don't need if we are exiting
{
// The hbmaster will crap out if the packet is malformed
// which might happen if the buffer isn't large enough
// So first copy dump the keys into a temporary buffer
struct qr2_buffer_s temp;
memcpy(temp.buffer, buf.buffer, (size_t)buf.len);
temp.len = buf.len;
qr_build_query_reply(qrec, &temp, 0xFF, NULL, 0xFF, NULL, 0xFF, NULL);
// If we maxxed out the packet, try again using only the server keys
if(AVAILABLE_BUFFER_LEN(&temp) < 1)
{
temp.len = buf.len;
qr_build_query_reply(qrec, &temp, 0xFF, NULL, 0, NULL, 0, NULL);
}
// copy temp back into buffer
memcpy(buf.buffer, temp.buffer, (size_t)temp.len);
buf.len = temp.len;
}
else
{
// PANTS - 2002.6.28
// add an extra NUL to end the server keys
if (AVAILABLE_BUFFER_LEN(&buf) >= 1)
buf.buffer[buf.len++] = 0;
}
//ret = (int)sendto(qrec->hbsock, buf.buffer, buf.len, 0, (struct sockaddr *)&(qrec->hbaddr), sizeof(struct sockaddr_in));
sendto(qrec->hbsock, buf.buffer, buf.len, 0, (struct sockaddr *)&(qrec->hbaddr), sizeof(struct sockaddr_in));
//set the ka time and hb time to now
qrec->lastka = qrec->lastheartbeat = current_time();
// clear the pending heartbeat request flag
if (statechanged != 0)
qrec->userstatechangerequested = 0;
gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
"Sent heartbeat to master (size %d)\r\n", buf.len);
gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump,
buf.buffer, buf.len);
}
#ifdef __cplusplus
}
#endif