openmohaa/code/gamespy/sv_gqueryreporting.c

545 lines
12 KiB
C
Raw Normal View History

2023-02-05 13:27:22 +01:00
/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
2023-02-05 01:40:14 +01:00
#include "sv_gqueryreporting.h"
#include "common/gsPlatformSocket.h"
#include "common/gsPlatformUtil.h"
#include "gutil.h"
2023-02-05 01:40:14 +01:00
static char* queries[] =
{
"basic",
"info",
"rules",
"players",
"status",
"packets",
"echo",
"secure"
};
static qr_implementation_t static_rec;
static qr_implementation_t* current_rec = &static_rec;
struct sockaddr_in hbaddr;
char qr_hostname[64];
void qr_check_queries(qr_t qrec);
void qr_check_send_heartbeat(qr_t qrec);
static void parse_query(qr_t qrec, char* query, struct sockaddr* sender);
static void send_heartbeat(qr_t qrec, int statechanged);
void qr_process_queries(qr_t qrec)
{
if (!qrec)
qrec = current_rec;
qr_check_send_heartbeat(qrec);
qr_check_queries(qrec);
}
void qr_process_queries_no_heartbeat(qr_t qrec)
{
if (!qrec)
qrec = current_rec;
qr_check_queries(qrec);
}
void qr_check_queries(qr_t qrec)
{
#define INBUF_LEN 256
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
while (CanReceiveOnSocket((SOCKET)qrec->hbsock))
{
//else we have data
error = (int)recvfrom((SOCKET)qrec->hbsock, indata, (INBUF_LEN - 1), 0, (struct sockaddr*)&saddr, &saddrlen);
if (gsiSocketIsNotError(error))
{
indata[error] = '\0';
parse_query(qrec, indata, (struct sockaddr*)&saddr);
}
}
}
void qr_check_send_heartbeat(qr_t qrec)
{
unsigned long tc;
tc = current_time();
if ((SOCKET)qrec->hbsock != INVALID_SOCKET)
{
if (tc - qrec->lastheartbeat <= 300000 && qrec->lastheartbeat && tc >= qrec->lastheartbeat)
{
if (qrec->no_query > 0 && tc - qrec->lastheartbeat > 30000)
{
send_heartbeat(qrec, 0);
if (qrec->no_query++ > 10)
qrec->no_query = 0;
}
}
else
{
send_heartbeat(qrec, 0);
}
}
}
void qr_send_statechanged(qr_t qrec)
{
if (!qrec)
qrec = current_rec;
send_heartbeat(qrec, 1);
}
void qr_shutdown(qr_t qrec)
{
if (qrec == NULL)
qrec = current_rec;
if (INVALID_SOCKET != (SOCKET)qrec->hbsock && (SOCKET)qrec->querysock) //if we own the socket
{
closesocket((SOCKET)qrec->hbsock);
}
qrec->hbsock = (void*)INVALID_SOCKET;
qrec->lastheartbeat = 0;
if (qrec->querysock) //if we own the socket
{
SocketShutDown();
}
if (qrec != &static_rec) //need to free it, it was dynamically allocated
{
free(qrec);
}
}
/* 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;
}
/* value_for_key: this returns a value for a certain key in s, where s is a string
containing key\value pairs. If the key does not exist, it returns NULL.
Note: the value is stored in a common buffer. If you want to keep it, make a copy! */
static char* value_for_key(const char* s, const char* key)
{
static int valueindex;
char* pos, * pos2;
char keyspec[256] = "\\";
static char value[2][256];
valueindex ^= 1;
strcat(keyspec, key);
strcat(keyspec, "\\");
pos = strstr(s, keyspec);
if (!pos)
return NULL;
pos += strlen(keyspec);
pos2 = value[valueindex];
while (*pos && *pos != '\\')
*pos2++ = *pos++;
*pos2 = '\0';
return value[valueindex];
}
static void packet_send(qr_t qrec, struct sockaddr* addr, char* buffer)
{
char keyvalue[80];
if (!strlen(buffer))
return;
sprintf(keyvalue, "\\queryid\\%d.%d", qrec->queryid, qrec->packetnumber++);
strcat(buffer, keyvalue);
sendto((SOCKET)qrec->querysock, buffer, (int)strlen(buffer), 0, addr, 16);
*buffer = 0;
}
static void buffer_send(qr_t qrec, struct sockaddr* sender, char* buffer, char* newdata)
{
int bcount;
int i;
char* lastkey;
char* pos;
bcount = 0;
if (strlen(newdata) + strlen(buffer) < 1350)
{
strcat(buffer, newdata);
return;
}
pos = newdata;
while (strlen(pos) > 1350)
{
lastkey = pos;
for (i = 0; i < 1350; ++i)
{
if (pos[i] == '\\')
{
if (!(bcount % 2))
lastkey = pos + i;
++bcount;
}
}
if (lastkey == pos)
return;
*lastkey = 0;
buffer_send(qrec, sender, buffer, pos);
*lastkey = '\\';
pos = lastkey;
bcount = 0;
if (strlen(buffer) + strlen(lastkey) < 1350)
{
strcat(buffer, pos);
return;
}
}
packet_send(qrec, sender, buffer);
strcpy(buffer, pos);
}
static void send_basic(qr_t qrec, struct sockaddr* sender, char* outbuf)
{
char keyvalue[1400] = { 0 };
qrec->qr_basic_callback(keyvalue, 1400, qrec->udata);
buffer_send(qrec, sender, outbuf, keyvalue);
}
static void send_info(qr_t qrec, struct sockaddr* sender, char* outbuf)
{
char keyvalue[1400] = { 0 };
qrec->qr_info_callback(keyvalue, 1400, qrec->udata);
buffer_send(qrec, sender, outbuf, keyvalue);
}
static void send_rules(qr_t qrec, struct sockaddr* sender, char* outbuf)
{
char keyvalue[1400] = { 0 };
qrec->qr_rules_callback(keyvalue, 1400, qrec->udata);
buffer_send(qrec, sender, outbuf, keyvalue);
}
static void send_players(qr_t qrec, struct sockaddr* sender, char* outbuf)
{
char keyvalue[1400] = { 0 };
qrec->qr_players_callback(keyvalue, 1400, qrec->udata);
buffer_send(qrec, sender, outbuf, keyvalue);
}
static void send_echo(qr_t qrec, struct sockaddr* sender, char* outbuf, const char* echostr)
{
char keyvalue[1400] = { 0 };
if (strlen(echostr) <= 50)
{
sprintf(keyvalue, "\\echoresponse\\%s", echostr);
buffer_send(qrec, sender, outbuf, keyvalue);
}
}
static void send_final(qr_t qrec, struct sockaddr* sender, char* outbuf, char* validation)
{
char keyvalue[256];
char encrypted_val[128];
char encoded_val[200];
int keylen;
if (*validation)
{
keylen = (int)strlen(validation);
if (keylen > 128)
return;
strcpy(encrypted_val, validation);
gs_encrypt((uchar*)qrec->secret_key, (int)strlen(qrec->secret_key), encrypted_val, keylen);
gs_encode(encrypted_val, keylen, encoded_val);
sprintf(keyvalue, "\\validate\\%s", encoded_val);
buffer_send(qrec, sender, outbuf, keyvalue);
}
sprintf(keyvalue, "\\final\\");
buffer_send(qrec, sender, outbuf, keyvalue);
packet_send(qrec, sender, outbuf);
}
static void parse_query(qr_t qrec, char* query, struct sockaddr* sender)
{
query_t querytype = qtunknown;
char buffer[1400] = { 0 };
const char* value;
char validation[256] = { 0 };
if (!qrec)
qrec = current_rec;
if (*query == ';')
{
// custom handler
qrec->qr_custom_handler(query, sender);
return;
}
qrec->packetnumber = 0;
qrec->queryid++;
if (qrec->no_query > 0)
qrec->no_query = 0;
for (querytype = qtbasic; querytype <= qtsecure; ++querytype)
{
value = value_for_key(query, queries[querytype]);
if (value)
{
switch (querytype)
{
case qtbasic:
send_basic(qrec, sender, buffer);
break;
case qtinfo:
send_info(qrec, sender, buffer);
break;
case qtrules:
send_rules(qrec, sender, buffer);
break;
case qtplayers:
send_players(qrec, sender, buffer);
break;
case qtstatus:
send_basic(qrec, sender, buffer);
send_info(qrec, sender, buffer);
send_rules(qrec, sender, buffer);
send_players(qrec, sender, buffer);
break;
case qtpackets:
send_basic(qrec, sender, buffer);
packet_send(qrec, sender, buffer);
send_info(qrec, sender, buffer);
packet_send(qrec, sender, buffer);
send_rules(qrec, sender, buffer);
packet_send(qrec, sender, buffer);
send_players(qrec, sender, buffer);
break;
case qtecho:
send_echo(qrec, sender, buffer, value);
break;
case qtsecure:
strcpy(validation, value);
break;
default:
continue;
}
}
}
send_final(qrec, sender, buffer, validation);
}
static void send_heartbeat(qr_t qrec, int statechanged)
{
char buf[256];
sprintf(buf, "\\heartbeat\\%d\\gamename\\%s", qrec->qport, qrec->gamename);
if (statechanged)
sprintf(buf + strlen(buf), "\\statechanged\\%d", statechanged);
2023-02-05 01:40:14 +01:00
sendto((SOCKET)qrec->hbsock, buf, (int)strlen(buf), 0, (const struct sockaddr*)&qrec->master_saddr, 16);
2023-02-05 01:40:14 +01:00
qrec->lastheartbeat = current_time();
}
static int do_connect(void* sock, char* addr, int port, struct sockaddr_in* master_saddr)
2023-02-05 01:40:14 +01:00
{
get_sockaddrin(addr, port, master_saddr, NULL);
2023-02-05 01:40:14 +01:00
return 0;
}
void init_qrec(
qr_t* qrec,
int baseport,
SOCKET hbsock,
SOCKET querysock,
char* gamename,
char* secret_key,
qr_querycallback_t qr_basic_callback,
qr_querycallback_t qr_info_callback,
qr_querycallback_t qr_rules_callback,
qr_querycallback_t qr_players_callback,
void* userdata
)
{
qr_t qr;
if (qrec)
{
qr = (qr_t)malloc(sizeof(qr_implementation_t));
*qrec = qr;
}
else
{
qr = &static_rec;
}
strcpy(qr->gamename, gamename);
strcpy(qr->secret_key, secret_key);
qr->qport = baseport;
qr->hbsock = (void*)hbsock;
qr->querysock = (void*)querysock;
qr->queryid = 1;
qr->no_query = 1;
qr->udata = userdata;
qr->qr_basic_callback = qr_basic_callback;
qr->qr_info_callback = qr_info_callback;
qr->lastheartbeat = 0;
qr->packetnumber = 0;
qr->qr_players_callback = qr_players_callback;
qr->qr_rules_callback = qr_rules_callback;
qr->qr_custom_handler = 0;
}
#define NUM_PORTS_TO_TRY 100
int qr_init(
qr_t* qrec,
char* ip,
int baseport,
char* gamename,
char* secret_key,
qr_querycallback_t qr_basic_callback,
qr_querycallback_t qr_info_callback,
qr_querycallback_t qr_rules_callback,
qr_querycallback_t qr_players_callback,
void* userdata
)
{
struct sockaddr_in saddr;
SOCKET hbsock;
int maxport;
int lasterror = 0;
#if defined(_LINUX)
unsigned int saddrlen;
#else
int saddrlen;
#endif
hbsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (hbsock == INVALID_SOCKET) {
return 1;
}
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
{
return 2;
}
if (baseport == 0) //we bound it dynamically
{
saddrlen = sizeof(saddr);
lasterror = getsockname(hbsock, (struct sockaddr*)&saddr, &saddrlen);
if (lasterror)
{
return 2;
}
baseport = ntohs(saddr.sin_port);
}
init_qrec(
qrec,
baseport,
hbsock,
hbsock,
gamename,
secret_key,
qr_basic_callback,
qr_info_callback,
qr_rules_callback,
qr_players_callback,
userdata);
if (!qrec)
qrec = &current_rec;
return do_connect((void*)hbsock, "master.x-null.net", 27900, &(*qrec)->master_saddr);
2023-02-05 01:40:14 +01:00
}