2023-02-04 21:00:01 +01:00
|
|
|
/********
|
|
|
|
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 = ¤t_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 )
|
|
|
|
{
|
2023-06-22 18:32:15 +02:00
|
|
|
int counter;
|
2023-02-04 21:00:01 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
|