Overhaul versioncheck system

This commit is contained in:
RElesgoe 2018-02-10 03:01:12 -08:00
parent a78355e88a
commit b75ac5e39e
14 changed files with 2905 additions and 3369 deletions

View file

@ -4,7 +4,7 @@ set(OUTPUT_CONFS ad.json anongame_infos.conf address_translation.conf
bnissue.txt bnmaps.conf bnxpcalc.conf
bnxplevel.conf channel.conf command_groups.conf realm.conf
sql_DB_layout.conf supportfile.conf topics.conf
tournament.conf versioncheck.conf icons.conf)
tournament.conf versioncheck.json icons.conf)
foreach(CONF ${OUTPUT_CONFS})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${CONF}.in ${CMAKE_CURRENT_BINARY_DIR}/${CONF} @ONLY)
endforeach(CONF)
@ -20,7 +20,7 @@ if(WITH_BNETD)
bnetd_default_user.plain bnissue.txt bnmaps.conf
bnxpcalc.conf bnxplevel.conf channel.conf command_groups.conf
realm.conf sql_DB_layout.conf supportfile.conf topics.conf
tournament.conf versioncheck.conf icons.conf)
tournament.conf versioncheck.json icons.conf)
# special treatment for non .in files
install(FILES bnetd_default_user.cdb DESTINATION ${SYSCONFDIR})

View file

@ -85,7 +85,7 @@ mpqfile = "${SYSCONFDIR}/autoupdate.conf"
logfile = "${LOCALSTATEDIR}/bnetd.log"
realmfile = "${SYSCONFDIR}/realm.conf"
maildir = "${LOCALSTATEDIR}/bnmail"
versioncheck_file = "${SYSCONFDIR}/versioncheck.conf"
versioncheck_file = "${SYSCONFDIR}/versioncheck.json"
mapsfile = "${SYSCONFDIR}/bnmaps.conf"
xplevelfile = "${SYSCONFDIR}/bnxplevel.conf"
xpcalcfile = "${SYSCONFDIR}/bnxpcalc.conf"
@ -232,18 +232,10 @@ allow_bad_version = true
# is a good idea.
allow_unknown_version = true
# This defines how the exeinfo field in the versioncheck file is being
# checked. You can choose between no match at all [none] (default),
# exact match [exact], exact case-sensitive match [exactcase], dumb wildcard
# match [wildcard], and parsed value comparison [parse].
# NOTE: [parse] needs the mktime() function and might therefore not work on
# every system.
version_exeinfo_match = none
# If you have choosen [parse] above, this is the tolerance with which
# the time can differ. The value must be given in seconds. If it's 0 this
# check is disabled.
version_exeinfo_maxdiff = 0
# Globally enable or disable versioncheck exeinfo matching
# If set to false, a client can pass any exeinfo string to pvpgn
# and will be allowed to connect.
version_exeinfo_match = true
# #
##############################################################################
@ -448,7 +440,7 @@ max_friends = 20
# Set track=0 to disable tracking. Any other number will set number
# of seconds between sending tracking packets. This is ON by default.
#track = 0
track = 60
track = 0
# Tracking server(s)
# Use a comma delimited list of hostnames with optional UDP port numbers

View file

@ -68,7 +68,7 @@ transfile = conf\address_translation.conf
mpqfile = conf\autoupdate.conf
logfile = var\bnetd.log
realmfile = conf\realm.conf
versioncheck_file = conf\versioncheck.conf
versioncheck_file = conf\versioncheck.json
mapsfile = conf\bnmaps.conf
xplevelfile = conf\bnxplevel.conf
xpcalcfile = conf\bnxpcalc.conf

File diff suppressed because it is too large Load diff

2163
conf/versioncheck.json.in Normal file

File diff suppressed because it is too large Load diff

View file

@ -72,7 +72,6 @@ namespace pvpgn
/**********************************************************************************/
static t_connection *_connlist_find_connection_by_uid(int uid);
static char const *_conn_get_versiontag(t_connection * c);
static int _anongame_gametype_to_queue(int type, int gametype);
static int _anongame_level_by_queue(t_connection * c, int queue);
@ -95,11 +94,6 @@ namespace pvpgn
return connlist_find_connection_by_account(accountlist_find_account_by_uid(uid));
}
static char const *_conn_get_versiontag(t_connection * c)
{
return versioncheck_get_versiontag(conn_get_versioncheck(c));
}
/**********/
static char const *_anongame_queue_to_string(int queue)
@ -541,7 +535,7 @@ namespace pvpgn
md = (t_matchdata*)xmalloc(sizeof(t_matchdata));
md->c = c;
md->map_prefs = map_prefs;
md->versiontag = _conn_get_versiontag(c);
md->versiontag = conn_get_versioncheck(c)->get_version_tag().c_str();
list_append_data(matchlists[queue][level], md);
@ -834,7 +828,10 @@ namespace pvpgn
LIST_TRAVERSE(matchlists[queue][level + delta], curr) {
md = (t_matchdata*)elem_get_data(curr);
if (md->versiontag && _conn_get_versiontag(c) && !std::strcmp(md->versiontag, _conn_get_versiontag(c)) && (cur_prefs & md->map_prefs)) {
if (md->versiontag
&& !std::strcmp(md->versiontag, conn_get_versioncheck(c)->get_version_tag().c_str())
&& (cur_prefs & md->map_prefs))
{
/* set maxlevel and minlevel to keep all players within 6 levels */
maxlevel = (level + delta + diff < maxlevel) ? level + delta + diff : maxlevel;
minlevel = (level + delta - diff > minlevel) ? level + delta - diff : minlevel;

View file

@ -402,7 +402,7 @@ namespace pvpgn
temp->protocol.client.clientexe = NULL;
temp->protocol.client.owner = NULL;
temp->protocol.client.cdkey = NULL;
temp->protocol.client.versioncheck = NULL;
temp->protocol.client.versioncheck = nullptr;
temp->protocol.account = NULL;
temp->protocol.chat.channel = NULL;
temp->protocol.chat.last_message = now;
@ -617,9 +617,6 @@ namespace pvpgn
if (c->protocol.account)
watchlist->dispatch(c->protocol.account, NULL, c->protocol.client.clienttag, Watch::ET_logout);
if (c->protocol.client.versioncheck)
versioncheck_destroy((t_versioncheck*)c->protocol.client.versioncheck); /* avoid warning */
if (c->protocol.chat.lastsender)
xfree((void *)c->protocol.chat.lastsender); /* avoid warning */
@ -3199,34 +3196,29 @@ namespace pvpgn
}
extern t_versioncheck * conn_get_versioncheck(t_connection * c)
const VersionCheck *conn_get_versioncheck(t_connection *c)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return NULL;
return nullptr;
}
return c->protocol.client.versioncheck;
}
extern int conn_set_versioncheck(t_connection * c, t_versioncheck * versioncheck)
bool conn_set_versioncheck(t_connection *c, const VersionCheck& versioncheck)
{
if (!c)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection");
return -1;
}
if (!versioncheck)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL versioncheck");
return -1;
return false;
}
c->protocol.client.versioncheck = versioncheck;
c->protocol.client.versioncheck = &versioncheck;
return 0;
return true;
}
extern int conn_get_echoback(t_connection * c)

View file

@ -150,7 +150,7 @@ namespace pvpgn
char const * clientexe;
char const * owner;
char const * cdkey;
t_versioncheck * versioncheck; /* equation and MPQ file used to validate game checksum */
const VersionCheck *versioncheck;
} client; /* client program specific data */
struct {
t_queue * outqueue; /* packets waiting to be sent */
@ -382,8 +382,8 @@ namespace pvpgn
extern int conn_quota_exceeded(t_connection * c, char const * message);
extern int conn_set_lastsender(t_connection * c, char const * sender);
extern char const * conn_get_lastsender(t_connection const * c);
extern t_versioncheck * conn_get_versioncheck(t_connection * c);
extern int conn_set_versioncheck(t_connection * c, t_versioncheck * versioncheck);
const VersionCheck *conn_get_versioncheck(t_connection *c);
bool conn_set_versioncheck(t_connection *c, const VersionCheck& versioncheck);
extern int conn_get_echoback(t_connection * c);
extern void conn_set_echoback(t_connection * c, int echoback);
extern int conn_set_ircline(t_connection * c, char const * line);

View file

@ -29,6 +29,7 @@
#include <cstdint>
#include <cstring>
#include <fstream>
#include <limits>
#include <sstream>
#include "compat/strcasecmp.h"
@ -570,11 +571,14 @@ namespace pvpgn
packet_del_ref(rpacket);
}
if ((rpacket = packet_create(packet_class_bnet))) {
t_versioncheck *vc;
if ((rpacket = packet_create(packet_class_bnet)))
{
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selecting version check", conn_get_socket(c));
vc = versioncheck_create(conn_get_archtag(c), conn_get_clienttag(c));
const VersionCheck& vc = select_versioncheck_entry(
conn_get_archtag(c),
conn_get_clienttag(c),
conn_get_versionid(c));
conn_set_versioncheck(c, vc);
packet_set_size(rpacket, sizeof(t_server_authreq_109));
packet_set_type(rpacket, SERVER_AUTHREQ_109);
@ -588,10 +592,10 @@ namespace pvpgn
bn_int_set(&rpacket->u.server_authreq_109.sessionkey, conn_get_sessionkey(c));
bn_int_set(&rpacket->u.server_authreq_109.sessionnum, conn_get_sessionnum(c));
file_to_mod_time(c, versioncheck_get_mpqfile(vc), &rpacket->u.server_authreq_109.timestamp);
packet_append_string(rpacket, versioncheck_get_mpqfile(vc));
packet_append_string(rpacket, versioncheck_get_eqn(vc));
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selected \"{}\" \"{}\"", conn_get_socket(c), versioncheck_get_mpqfile(vc), versioncheck_get_eqn(vc));
file_to_mod_time(c, vc.get_checkrevision_filename().c_str(), &rpacket->u.server_authreq_109.timestamp);
packet_append_string(rpacket, vc.get_checkrevision_filename().c_str());
packet_append_string(rpacket, vc.get_equation().c_str());
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selected \"{}\" \"{}\"", conn_get_socket(c), vc.get_checkrevision_filename(), vc.get_equation());
if ((conn_get_clienttag(c) == CLIENTTAG_WARCRAFT3_UINT)
|| (conn_get_clienttag(c) == CLIENTTAG_WAR3XP_UINT)) {
char padding[128];
@ -646,19 +650,25 @@ namespace pvpgn
packet_del_ref(rpacket);
}
}
else {
t_versioncheck *vc;
else
{
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selecting version check", conn_get_socket(c));
vc = versioncheck_create(conn_get_archtag(c), conn_get_clienttag(c));
const VersionCheck& vc = select_versioncheck_entry(
conn_get_archtag(c),
conn_get_clienttag(c),
conn_get_versionid(c));
conn_set_versioncheck(c, vc);
if ((rpacket = packet_create(packet_class_bnet))) {
if ((rpacket = packet_create(packet_class_bnet)))
{
packet_set_size(rpacket, sizeof(t_server_authreq1));
packet_set_type(rpacket, SERVER_AUTHREQ1);
file_to_mod_time(c, versioncheck_get_mpqfile(vc), &rpacket->u.server_authreq1.timestamp);
packet_append_string(rpacket, versioncheck_get_mpqfile(vc));
packet_append_string(rpacket, versioncheck_get_eqn(vc));
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selected \"{}\" \"{}\"", conn_get_socket(c), versioncheck_get_mpqfile(vc), versioncheck_get_eqn(vc));
file_to_mod_time(c, vc.get_checkrevision_filename().c_str(), &rpacket->u.server_authreq1.timestamp);
packet_append_string(rpacket, vc.get_checkrevision_filename().c_str());
packet_append_string(rpacket, vc.get_equation().c_str());
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selected \"{}\" \"{}\"", conn_get_socket(c), vc.get_checkrevision_filename(), vc.get_equation());
conn_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
}
@ -1000,104 +1010,116 @@ namespace pvpgn
static int _client_authreq1(t_connection * c, t_packet const *const packet)
{
t_packet *rpacket;
if (packet_get_size(packet) < sizeof(t_client_authreq1)) {
if (packet_get_size(packet) < sizeof(t_client_authreq1))
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ1 packet (expected {} bytes, got {})", conn_get_socket(c), sizeof(t_client_authreq1), packet_get_size(packet));
return -1;
}
bool failed = false;
// The following if statements are sanity checks
// The client should have already sent this information in a previous packet and is resending it again in this packet
if (bn_int_get(packet->u.client_authreq1.archtag) != conn_get_archtag(c))
failed = true;
if (bn_int_get(packet->u.client_authreq1.clienttag) != conn_get_clienttag(c))
failed = true;
if (bn_int_get(packet->u.client_authreq1.versionid) != conn_get_versionid(c))
failed = true;
const char *exeinfo = packet_get_str_const(packet, sizeof(t_client_authreq1), MAX_EXEINFO_STR);
if (exeinfo)
{
char verstr[16];
char const *exeinfo;
char const *versiontag;
int failed;
failed = 0;
if (bn_int_get(packet->u.client_authreq1.archtag) != conn_get_archtag(c))
failed = 1;
if (bn_int_get(packet->u.client_authreq1.clienttag) != conn_get_clienttag(c))
failed = 1;
if (!(exeinfo = packet_get_str_const(packet, sizeof(t_client_authreq1), MAX_EXEINFO_STR))) {
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ1 (missing or too long exeinfo)", conn_get_socket(c));
exeinfo = "badexe";
failed = 1;
}
conn_set_versionid(c, bn_int_get(packet->u.client_authreq1.versionid));
conn_set_checksum(c, bn_int_get(packet->u.client_authreq1.checksum));
conn_set_gameversion(c, bn_int_get(packet->u.client_authreq1.gameversion));
std::strcpy(verstr, vernum_to_verstr(bn_int_get(packet->u.client_authreq1.gameversion)));
conn_set_clientver(c, verstr);
conn_set_clientexe(c, exeinfo);
}
else
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ1 (missing or too long exeinfo)", conn_get_socket(c));
conn_set_clientexe(c, "badexe");
failed = true;
}
eventlog(eventlog_level_info, __FUNCTION__, "[{}] CLIENT_AUTHREQ1 archtag=0x{:08x} clienttag=0x{:08x} verstr={} exeinfo=\"{}\" versionid=0x{:08x} gameversion=0x{:08x} checksum=0x{:08x}", conn_get_socket(c), bn_int_get(packet->u.client_authreq1.archtag), bn_int_get(packet->u.client_authreq1.clienttag), verstr, exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c));
std::string version_string = vernum_to_verstr(bn_int_get(packet->u.client_authreq1.gameversion));
conn_set_clientver(c, version_string.c_str());
eventlog(eventlog_level_info, __FUNCTION__, "[{}] CLIENT_AUTHREQ1 archtag=0x{:08x} clienttag=0x{:08x} verstr={} exeinfo=\"{}\" versionid=0x{:08x} gameversion=0x{:08x} checksum=0x{:08x}", conn_get_socket(c), bn_int_get(packet->u.client_authreq1.archtag), bn_int_get(packet->u.client_authreq1.clienttag), version_string, exeinfo == nullptr ? "badexe" : exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c));
conn_set_versionid(c, bn_int_get(packet->u.client_authreq1.versionid));
conn_set_checksum(c, bn_int_get(packet->u.client_authreq1.checksum));
conn_set_gameversion(c, bn_int_get(packet->u.client_authreq1.gameversion));
if ((rpacket = packet_create(packet_class_bnet))) {
packet_set_size(rpacket, sizeof(t_server_authreply1));
packet_set_type(rpacket, SERVER_AUTHREPLY1);
if (!conn_get_versioncheck(c) && prefs_get_skip_versioncheck())
eventlog(eventlog_level_info, __FUNCTION__, "[{}] skip versioncheck enabled and client did not request validation", conn_get_socket(c));
if (!conn_get_versioncheck(c) && prefs_get_skip_versioncheck())
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] skip versioncheck enabled and client did not request validation", conn_get_socket(c));
}
else if (prefs_get_allow_unknown_version())
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] skipping versioncheck validation because allow_unknown_version is true", conn_get_socket(c));
}
else
{
if (conn_get_versioncheck(c))
{
if (conn_get_versioncheck(c)->validate_checkrevision_data(bn_int_get(packet->u.client_authreq1.gameversion), bn_int_get(packet->u.client_authreq1.checksum), exeinfo == nullptr ? "badexe" : exeinfo))
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client matches versiontag \"{}\"", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag());
}
else
switch (versioncheck_validate(conn_get_versioncheck(c), conn_get_archtag(c), conn_get_clienttag(c), exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c))) {
case -1: /* failed test... client has been modified */
if (!prefs_get_allow_bad_version()) {
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed test (marking untrusted)", conn_get_socket(c));
failed = 1;
}
else
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed test, allowing anyway", conn_get_socket(c));
break;
case 0: /* not listed in table... can't tell if client has been modified */
if (!prefs_get_allow_unknown_version()) {
eventlog(eventlog_level_info, __FUNCTION__, "[{}] unable to test client (marking untrusted)", conn_get_socket(c));
failed = 1;
}
else
eventlog(eventlog_level_info, __FUNCTION__, "[{}] unable to test client, allowing anyway", conn_get_socket(c));
break;
/* 1 == test passed... client seems to be ok */
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed versioncheck", conn_get_socket(c));
failed = true;
}
versiontag = versioncheck_get_versiontag(conn_get_versioncheck(c));
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client matches versiontag \"{}\"", conn_get_socket(c), versiontag);
if (failed) {
conn_set_state(c, conn_state_untrusted);
bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_BADVERSION);
packet_append_string(rpacket, "");
}
else {
char *mpqfilename;
mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), versiontag, NULL);
/* Only handle updates when there is an update file available. */
if (mpqfilename != NULL) {
eventlog(eventlog_level_info, __FUNCTION__, "[{}] an upgrade for version {} is available \"{}\"", conn_get_socket(c), versioncheck_get_versiontag(conn_get_versioncheck(c)), mpqfilename);
bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_UPDATE);
packet_append_string(rpacket, mpqfilename);
}
else {
eventlog(eventlog_level_info, __FUNCTION__, "[{}] no upgrade for {} is available", conn_get_socket(c), versioncheck_get_versiontag(conn_get_versioncheck(c)));
bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_OK);
packet_append_string(rpacket, "");
}
if (mpqfilename)
xfree((void *)mpqfilename);
}
packet_append_string(rpacket, ""); /* FIXME: what's the second string for? */
conn_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client did not request validation", conn_get_socket(c));
}
}
t_packet *rpacket = packet_create(packet_class_bnet);
if (!rpacket)
{
return -1;
}
packet_set_size(rpacket, sizeof(t_server_authreply1));
packet_set_type(rpacket, SERVER_AUTHREPLY1);
if (failed)
{
conn_set_state(c, conn_state_untrusted);
bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_BADVERSION);
packet_append_string(rpacket, "");
}
else
{
char *mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), conn_get_versioncheck(c)->get_version_tag().c_str(), nullptr);
/* Only handle updates when there is an update file available. */
if (mpqfilename != nullptr)
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] an upgrade for version {} is available \"{}\"", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag(), mpqfilename);
bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_UPDATE);
packet_append_string(rpacket, mpqfilename);
xfree(static_cast<void *>(mpqfilename));
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] no upgrade for {} is available", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag());
bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_OK);
packet_append_string(rpacket, "");
}
}
// FIXME: what's the second string for?
packet_append_string(rpacket, "");
conn_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
return 0;
}
@ -1112,10 +1134,8 @@ namespace pvpgn
{
char verstr[16];
char const *exeinfo;
char const *versiontag;
int failed;
char const *owner;
unsigned int count;
unsigned int pos;
@ -1123,19 +1143,23 @@ namespace pvpgn
count = bn_int_get(packet->u.client_authreq_109.cdkey_number);
pos = sizeof(t_client_authreq_109)+(count * sizeof(t_cdkey_info));
if (!(exeinfo = packet_get_str_const(packet, pos, MAX_EXEINFO_STR))) {
const char * const exeinfo = packet_get_str_const(packet, pos, MAX_EXEINFO_STR);
if (!exeinfo)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 (missing or too long exeinfo)", conn_get_socket(c));
exeinfo = "badexe";
failed = 1;
}
conn_set_clientexe(c, exeinfo);
pos += std::strlen(exeinfo) + 1;
conn_set_clientexe(c, exeinfo != nullptr ? exeinfo : "badexe");
pos += std::strlen(exeinfo != nullptr ? exeinfo : "badexe") + 1;
if (!(owner = packet_get_str_const(packet, pos, MAX_OWNER_STR))) {
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 (missing or too long owner)", conn_get_socket(c));
owner = ""; /* maybe owner was missing, use empty string */
{
const char * const owner = packet_get_str_const(packet, pos, MAX_OWNER_STR);
if (!owner) // maybe owner was missing, use empty string
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 (missing or too long owner)", conn_get_socket(c));
}
conn_set_owner(c, owner != nullptr ? owner : "");
}
conn_set_owner(c, owner);
conn_set_checksum(c, bn_int_get(packet->u.client_authreq_109.checksum));
conn_set_gameversion(c, bn_int_get(packet->u.client_authreq_109.gameversion));
@ -1143,63 +1167,67 @@ namespace pvpgn
conn_set_clientver(c, verstr);
conn_set_clientexe(c, exeinfo);
eventlog(eventlog_level_info, __FUNCTION__, "[{}] CLIENT_AUTHREQ_109 ticks=0x{:08x}, verstr={} exeinfo=\"{}\" versionid=0x{:08x} gameversion=0x{:08x} checksum=0x{:08x}", conn_get_socket(c), bn_int_get(packet->u.client_authreq_109.ticks), verstr, exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c));
eventlog(eventlog_level_info, __FUNCTION__, "[{}] CLIENT_AUTHREQ_109 ticks=0x{:08x}, verstr={} exeinfo=\"{}\" versionid=0x{:08x} gameversion=0x{:08x} checksum=0x{:08x}", conn_get_socket(c), bn_int_get(packet->u.client_authreq_109.ticks), verstr, exeinfo != nullptr ? exeinfo : "badexe", conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c));
if ((rpacket = packet_create(packet_class_bnet))) {
if ((rpacket = packet_create(packet_class_bnet)))
{
packet_set_size(rpacket, sizeof(t_server_authreply_109));
packet_set_type(rpacket, SERVER_AUTHREPLY_109);
if (!conn_get_versioncheck(c) && prefs_get_skip_versioncheck())
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] skip versioncheck enabled and client did not request validation", conn_get_socket(c));
}
else if (prefs_get_allow_unknown_version())
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] skipping versioncheck validation because allow_unknown_version is true", conn_get_socket(c));
}
else
switch (versioncheck_validate(conn_get_versioncheck(c), conn_get_archtag(c), conn_get_clienttag(c), exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c))) {
case -1: /* failed test... client has been modified */
if (!prefs_get_allow_bad_version()) {
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed test (closing connection)", conn_get_socket(c));
failed = 1;
{
if (conn_get_versioncheck(c))
{
if (conn_get_versioncheck(c)->validate_checkrevision_data(bn_int_get(packet->u.client_authreq1.gameversion), bn_int_get(packet->u.client_authreq1.checksum), exeinfo == nullptr ? "badexe" : exeinfo))
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client matches versiontag \"{}\"", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag());
}
else
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed test, allowing anyway", conn_get_socket(c));
break;
case 0: /* not listed in table... can't tell if client has been modified */
if (!prefs_get_allow_unknown_version()) {
eventlog(eventlog_level_info, __FUNCTION__, "[{}] unable to test client (closing connection)", conn_get_socket(c));
failed = 1;
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed versioncheck", conn_get_socket(c));
failed = true;
}
else
eventlog(eventlog_level_info, __FUNCTION__, "[{}] unable to test client, allowing anyway", conn_get_socket(c));
break;
/* 1 == test passed... client seems to be ok */
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client did not request validation", conn_get_socket(c));
}
}
versiontag = versioncheck_get_versiontag(conn_get_versioncheck(c));
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client matches versiontag \"{}\"", conn_get_socket(c), versiontag);
if (failed) {
if (failed)
{
conn_set_state(c, conn_state_untrusted);
bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_BADVERSION);
packet_append_string(rpacket, "");
}
else {
char *mpqfilename;
mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), versiontag, NULL);
else
{
char *mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), conn_get_versioncheck(c)->get_version_tag().c_str(), NULL);
/* Only handle updates when there is an update file available. */
if (mpqfilename != NULL) {
eventlog(eventlog_level_info, __FUNCTION__, "[{}] an upgrade for {} is available \"{}\"", conn_get_socket(c), versiontag, mpqfilename);
if (mpqfilename != nullptr)
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] an upgrade for {} is available \"{}\"", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag(), mpqfilename);
bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_UPDATE);
packet_append_string(rpacket, mpqfilename);
xfree((void *)mpqfilename);
}
else {
eventlog(eventlog_level_info, __FUNCTION__, "[{}] no upgrade for {} is available", conn_get_socket(c), versiontag);
else
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] no upgrade for {} is available", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag());
bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_OK);
packet_append_string(rpacket, "");
}
if (mpqfilename)
xfree((void *)mpqfilename);
}
conn_push_outqueue(c, rpacket);
@ -2427,97 +2455,163 @@ namespace pvpgn
static int _client_atfriendscreen(t_connection * c, t_packet const *const packet)
{
char const *myusername;
char const *fname;
t_connection *dest_c;
unsigned char f_cnt = 0;
t_account *account;
t_packet *rpacket;
char const *vt;
char const *nvt;
t_friend *fr;
t_list *flist;
t_elem *curr;
t_channel *mychannel, *chan;
int publicchan = 1;
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] got CLIENT_ARRANGEDTEAM_FRIENDSCREEN packet", conn_get_socket(c));
eventlog(eventlog_level_info, __FUNCTION__, "[{}] got CLIENT_ARRANGEDTEAM_FRIENDSCREEN packet", conn_get_socket(c));
myusername = conn_get_username(c);
eventlog(eventlog_level_trace, "handle_bnet", "[{}] AT - Got Username %s", conn_get_socket(c), myusername);
if (!(rpacket = packet_create(packet_class_bnet))) {
eventlog(eventlog_level_error, "handle_bnet", "[{}] AT - can't create friendscreen server packet", conn_get_socket(c));
const char *my_username = conn_get_username(c);
if (!my_username)
{
return -1;
}
t_channel *my_channel = conn_get_channel(c);
int my_channel_is_public = 1;
if (my_channel)
{
my_channel_is_public = channel_get_flags(my_channel) & channel_flags_public;
}
t_packet *rpacket = packet_create(packet_class_bnet);
if (!rpacket)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] could not create friendscreen server packet", conn_get_socket(c));
return -1;
}
packet_set_size(rpacket, sizeof(t_server_arrangedteam_friendscreen));
packet_set_type(rpacket, SERVER_ARRANGEDTEAM_FRIENDSCREEN);
mychannel = conn_get_channel(c);
if ((mychannel))
publicchan = channel_get_flags(mychannel) & channel_flags_public;
std::uint8_t available_players = 0;
vt = versioncheck_get_versiontag(conn_get_versioncheck(c));
flist = account_get_friends(conn_get_account(c));
LIST_TRAVERSE(flist, curr) {
if (!(fr = (t_friend*)elem_get_data(curr))) {
// begin search for mutual and available friends
t_list *my_friend_list = account_get_friends(conn_get_account(c));
t_elem *entry;
LIST_TRAVERSE(my_friend_list, entry)
{
t_friend *_friend = static_cast<t_friend *>(elem_get_data(entry));
if (!_friend)
{
eventlog(eventlog_level_error, __FUNCTION__, "found NULL entry in list");
continue;
}
account = friend_get_account(fr);
if (!(dest_c = connlist_find_connection_by_account(account)))
continue; // if user is offline, then continue to next friend
nvt = versioncheck_get_versiontag(conn_get_versioncheck(dest_c));
if (vt && nvt && std::strcmp(vt, nvt))
continue; /* friend is using another game/version */
if (available_players == std::numeric_limits<decltype(available_players)>::max())
{
eventlog(eventlog_level_info, __FUNCTION__, "Reached maximum amount of available players to send in packet");
break;
}
if (friend_get_mutual(fr)) {
if (conn_get_dndstr(dest_c))
continue; // user is dnd
if (conn_get_awaystr(dest_c))
continue; // user is away
if (conn_get_game(dest_c))
continue; // user is some game
if (!(chan = conn_get_channel(dest_c)))
// skip non-mutual friends
if (friend_get_mutual(_friend) == FRIEND_NOTMUTUAL)
{
continue;
}
t_account *friend_account = friend_get_account(_friend);
t_connection *friend_connection = connlist_find_connection_by_account(friend_account);
// if user is offline, then continue to next friend
if (!friend_connection)
{
continue;
}
const std::string my_version_tag = conn_get_versioncheck(c) ? conn_get_versioncheck(c)->get_version_tag() : "";
const std::string friend_version_tag = conn_get_versioncheck(c) ? conn_get_versioncheck(friend_connection)->get_version_tag() : "";
// friend is using another game or is on a different version
if (my_version_tag != friend_version_tag)
{
continue;
}
// friend has Do Not Disturb mode enabled
if (conn_get_dndstr(friend_connection))
{
continue;
}
// friend is away
if (conn_get_awaystr(friend_connection))
{
continue;
}
// friend is not in a channel
t_channel *friend_channel = conn_get_channel(friend_connection);
if (!friend_channel)
{
continue;
}
// // don't list YET if in same private channel
if (!my_channel_is_public && (friend_channel == my_channel))
{
continue;
}
const char *friend_name = account_get_name(friend_account);
eventlog(eventlog_level_trace, __FUNCTION__, "Friend {} is available for an AT Game.", friend_name);
available_players += 1;
packet_append_string(rpacket, friend_name);
}
// now list matching users in same private chan
if (!my_channel_is_public)
{
for (t_connection *user_connection = channel_get_first(my_channel); user_connection; user_connection = channel_get_next())
{
if (available_players == std::numeric_limits<decltype(available_players)>::max())
{
eventlog(eventlog_level_info, __FUNCTION__, "Reached maximum amount of available players to send in packet");
break;
}
// skip self
if (user_connection == c)
{
continue;
if (!publicchan && (chan == mychannel))
continue; // don't list YET if in same private channel
}
fname = account_get_name(account);
eventlog(eventlog_level_trace, "handle_bnet", "AT - Friend: {} is available for a AT Game.", fname);
f_cnt++;
packet_append_string(rpacket, fname);
const std::string my_version_tag = conn_get_versioncheck(c) ? conn_get_versioncheck(c)->get_version_tag() : "";
const std::string user_version_tag = conn_get_versioncheck(c) ? conn_get_versioncheck(user_connection)->get_version_tag() : "";
// user is using another game or is on a different version
if (my_version_tag != user_version_tag)
{
continue;
}
// user is dnd
if (conn_get_dndstr(user_connection))
{
continue;
}
// user is away
if (conn_get_awaystr(user_connection))
{
continue;
}
const char *username = account_get_name(conn_get_account(user_connection));
if (!username)
{
continue;
}
eventlog(eventlog_level_trace, __FUNCTION__, "user {} in private channel {} is available for an AT Game.", username, channel_get_name(my_channel));
available_players += 1;
packet_append_string(rpacket, username);
}
}
if (!publicchan) { // now list matching users in same private chan
for (dest_c = channel_get_first(mychannel); dest_c; dest_c = channel_get_next()) {
if (dest_c == c)
continue; // don'tlist yourself
nvt = versioncheck_get_versiontag(conn_get_versioncheck(dest_c));
if (vt && nvt && std::strcmp(vt, nvt))
continue; /* user is using another game/version */
if (conn_get_dndstr(dest_c))
continue; // user is dnd
if (conn_get_awaystr(dest_c))
continue; // user is away
if (!(conn_get_account(dest_c)))
continue;
fname = account_get_name(conn_get_account(dest_c));
eventlog(eventlog_level_trace, "handle_bnet", "AT - user in private channel: {} is available for a AT Game.", fname);
f_cnt++;
packet_append_string(rpacket, fname);
}
if (available_players == 0)
{
eventlog(eventlog_level_info, __FUNCTION__, "No available players");
}
if (!f_cnt)
eventlog(eventlog_level_info, "handle_bnet", "AT - no friends available for AT game.");
bn_byte_set(&rpacket->u.server_arrangedteam_friendscreen.f_count, f_cnt);
bn_byte_set(&rpacket->u.server_arrangedteam_friendscreen.f_count, available_players);
conn_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
return 0;
@ -3653,12 +3747,12 @@ namespace pvpgn
}
if (conn_get_versioncheck(cbdata->c) &&
conn_get_versioncheck(game_get_owner(game)) &&
versioncheck_get_versiontag(conn_get_versioncheck(cbdata->c)) &&
versioncheck_get_versiontag(conn_get_versioncheck(game_get_owner(game))) &&
std::strcmp(versioncheck_get_versiontag(conn_get_versioncheck(cbdata->c)), versioncheck_get_versiontag(conn_get_versioncheck(game_get_owner(game)))) != 0) {
conn_get_versioncheck(cbdata->c)->get_version_tag() != conn_get_versioncheck(game_get_owner(game))->get_version_tag())
{
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] not listing because game is wrong versiontag", conn_get_socket(cbdata->c));
return 0;
}
bn_short_set(&glgame.gametype, gtype_to_bngtype(game_get_type(game)));
bn_short_set(&glgame.unknown1, SERVER_GAMELISTREPLY_GAME_UNKNOWN1);
bn_short_set(&glgame.unknown3, SERVER_GAMELISTREPLY_GAME_UNKNOWN3);
@ -4716,33 +4810,23 @@ namespace pvpgn
static int _client_changeclient(t_connection * c, t_packet const *const packet)
{
t_versioncheck *vc;
if (packet_get_size(packet) < sizeof(t_client_changeclient)) {
if (packet_get_size(packet) < sizeof(t_client_changeclient))
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad CLIENT_CHANGECLIENT packet (expected {} bytes, got {})", conn_get_socket(c), sizeof(t_client_changeclient), packet_get_size(packet));
return -1;
}
if (tag_check_in_list(bn_int_get(packet->u.client_changeclient.clienttag), prefs_get_allowed_clients())) {
if (conn_get_clienttag(c) != CLIENTTAG_WAR3XP_UINT
|| bn_int_get(packet->u.client_changeclient.clienttag) != CLIENTTAG_WARCRAFT3_UINT)
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] invalid attempt to change client from {X} to {X}", conn_get_socket(c), conn_get_clienttag(c), bn_int_get(packet->u.client_changeclient.clienttag));
conn_set_state(c, conn_state_destroy);
return 0;
return -1;
}
conn_set_clienttag(c, bn_int_get(packet->u.client_changeclient.clienttag));
vc = conn_get_versioncheck(c);
versioncheck_set_versiontag(vc, clienttag_uint_to_str(conn_get_clienttag(c)));
if (vc && versioncheck_get_versiontag(vc)) {
switch (versioncheck_validate(vc, conn_get_archtag(c), conn_get_clienttag(c), conn_get_clientexe(c), conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c))) {
case -1: /* failed test... client has been modified */
case 0: /* not listed in table... can't tell if client has been modified */
eventlog(eventlog_level_error, __FUNCTION__, "[{}] error revalidating, allowing anyway", conn_get_socket(c));
break;
}
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client versiontag set to \"{}\"", conn_get_socket(c), versioncheck_get_versiontag(vc));
}
eventlog(eventlog_level_info, __FUNCTION__, "[{}] changed client to {X}", conn_get_socket(c), bn_int_get(packet->u.client_changeclient.clienttag));
return 0;
}

View file

@ -380,8 +380,12 @@ int pre_server_startup(void)
if (autoupdate_load(prefs_get_mpqfile()) < 0)
eventlog(eventlog_level_error, __FUNCTION__, "could not load autoupdate list");
if (versioncheck_load(prefs_get_versioncheck_file()) < 0)
if (!load_versioncheck_conf(prefs_get_versioncheck_file()))
{
eventlog(eventlog_level_error, __FUNCTION__, "could not load versioncheck list");
}
if (news_load(prefs_get_newsfile()) < 0)
eventlog(eventlog_level_error, __FUNCTION__, "could not load news list");
watchlist.reset(new WatchComponent());
@ -449,7 +453,7 @@ void post_server_shutdown(int status)
attrlayer_cleanup();
watchlist.reset();
news_unload();
versioncheck_unload();
unload_versioncheck_conf();
autoupdate_unload();
ipbanlist_save(prefs_get_ipbanfile());
ipbanlist_destroy();

View file

@ -1415,9 +1415,11 @@ namespace pvpgn
if (do_restart == restart_mode_all || do_restart == restart_mode_versioncheck)
{
versioncheck_unload();
if (versioncheck_load(prefs_get_versioncheck_file()) < 0)
unload_versioncheck_conf();
if (!load_versioncheck_conf(prefs_get_versioncheck_file()))
{
eventlog(eventlog_level_error, __FUNCTION__, "could not load versioncheck list");
}
}
if (do_restart == restart_mode_all || do_restart == restart_mode_ipbans)

View file

@ -22,638 +22,393 @@
#define VERSIONCHECK_INTERNAL_ACCESS
#include "versioncheck.h"
#include <ctime>
#include <cstring>
#include <cstdio>
#include <cassert>
#include <cctype>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <forward_list>
#include <fstream>
#include <iomanip>
#include <regex>
#include <sstream>
#include <string>
#include "compat/strcasecmp.h"
#include "common/list.h"
#include "common/xalloc.h"
#include "common/format.h"
#include "common/eventlog.h"
#include "common/util.h"
#include "common/field_sizes.h"
#include "common/token.h"
#include "common/proginfo.h"
#include "common/xstring.h"
#include "json/json.hpp"
#include "prefs.h"
#include "common/setup_after.h"
using json = nlohmann::json;
namespace pvpgn
{
namespace bnetd
{
static t_list * versioninfo_head = NULL;
static t_versioncheck dummyvc = { "A=42 B=42 C=42 4 A=A^S B=B^B C=C^C A=A^S", "IX86ver1.mpq", "NoVC" };
std::forward_list<VersionCheck> vc_entries = {};
static int versioncheck_compare_exeinfo(t_parsed_exeinfo * pattern, t_parsed_exeinfo * match);
bool versioncheck_conf_is_loaded = false;
extern t_versioncheck * versioncheck_create(t_tag archtag, t_tag clienttag)
struct file_metadata parse_file_metadata(const std::string& unparsed_metadata);
bool compare_file_metadata(const struct file_metadata& pattern, const struct file_metadata& match, bool skip_timestamp_match);
bool load_versioncheck_conf(const std::string& filename)
{
t_elem const * curr;
t_versioninfo * vi;
t_versioncheck * vc;
char archtag_str[5];
char clienttag_str[5];
LIST_TRAVERSE_CONST(versioninfo_head, curr)
if (versioncheck_conf_is_loaded)
{
if (!(vi = (t_versioninfo*)elem_get_data(curr))) /* should not happen */
{
eventlog(eventlog_level_error, __FUNCTION__, "version list contains NULL item");
continue;
}
eventlog(eventlog_level_debug, __FUNCTION__, "version check entry archtag={}, clienttag={}",
tag_uint_to_str(archtag_str, vi->archtag),
tag_uint_to_str(clienttag_str, vi->clienttag));
if (vi->archtag != archtag)
continue;
if (vi->clienttag != clienttag)
continue;
/* FIXME: randomize the selection if more than one match */
vc = (t_versioncheck*)xmalloc(sizeof(t_versioncheck));
vc->eqn = xstrdup(vi->eqn);
vc->mpqfile = xstrdup(vi->mpqfile);
vc->versiontag = xstrdup(tag_uint_to_str(clienttag_str, clienttag));
return vc;
eventlog(eventlog_level_error, __FUNCTION__, "Could not load {}, a versioncheck configuration file is already loaded", filename);
return false;
}
/*
* No entries in the file that match, return the dummy because we have to send
* some equation and auth mpq to the client. The client is not going to pass the
* validation later unless skip_versioncheck or allow_unknown_version is enabled.
*/
return &dummyvc;
}
extern int versioncheck_destroy(t_versioncheck * vc)
{
if (!vc)
std::ifstream file_stream(filename, std::ios::in);
if (!file_stream.is_open())
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc");
return -1;
eventlog(eventlog_level_error, __FUNCTION__, "Could not open file \"{}\" for reading", filename);
return false;
}
if (vc == &dummyvc)
return 0;
xfree((void *)vc->versiontag);
xfree((void *)vc->mpqfile);
xfree((void *)vc->eqn);
xfree(vc);
return 0;
}
extern int versioncheck_set_versiontag(t_versioncheck * vc, char const * versiontag)
{
if (!vc) {
eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc");
return -1;
}
if (!versiontag) {
eventlog(eventlog_level_error, __FUNCTION__, "got NULL versiontag");
return -1;
}
if (vc->versiontag != NULL) xfree((void *)vc->versiontag);
vc->versiontag = xstrdup(versiontag);
return 0;
}
extern char const * versioncheck_get_versiontag(t_versioncheck const * vc)
{
if (!vc) {
eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc");
return NULL;
}
return vc->versiontag;
}
extern char const * versioncheck_get_mpqfile(t_versioncheck const * vc)
{
if (!vc)
json j;
try
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc");
return NULL;
file_stream >> j;
}
return vc->mpqfile;
}
extern char const * versioncheck_get_eqn(t_versioncheck const * vc)
{
if (!vc)
catch (const std::exception& e)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc");
return NULL;
eventlog(eventlog_level_error, __FUNCTION__, "Could not parse JSON data: {}", e.what());
return false;
}
return vc->eqn;
}
t_parsed_exeinfo * parse_exeinfo(char const * exeinfo)
{
t_parsed_exeinfo * parsed_exeinfo;
if (!exeinfo) {
return NULL;
}
parsed_exeinfo = (t_parsed_exeinfo*)xmalloc(sizeof(t_parsed_exeinfo));
parsed_exeinfo->exe = xstrdup(exeinfo);
parsed_exeinfo->time = 0;
parsed_exeinfo->size = 0;
if (std::strcmp(prefs_get_version_exeinfo_match(), "parse") == 0)
int success_count = 0;
int total_count = 0;
for (auto jclient : json::iterator_wrapper(j))
{
struct std::tm t1;
char *exe;
char mask[MAX_EXEINFO_STR + 1];
char * marker;
unsigned size;
char time_invalid = 0;
if ((exeinfo[0] == '\0') || //happens when using war3-noCD and having deleted war3.org
(std::strcmp(exeinfo, "badexe") == 0)) //happens when AUTHREQ had no owner/exeinfo entry
total_count += jclient.value().size();
for (const auto& entry : jclient.value())
{
xfree((void *)parsed_exeinfo->exe);
xfree((void *)parsed_exeinfo);
eventlog(eventlog_level_error, __FUNCTION__, "found empty exeinfo");
return NULL;
}
std::memset(&t1, 0, sizeof(t1));
t1.tm_isdst = -1;
exeinfo = strreverse((char *)exeinfo);
if (!(marker = std::strchr((char *)exeinfo, ' ')))
{
xfree((void *)parsed_exeinfo->exe);
xfree((void *)parsed_exeinfo);
return NULL;
}
for (; marker[0] == ' '; marker++);
if (!(marker = std::strchr(marker, ' ')))
{
xfree((void *)parsed_exeinfo->exe);
xfree((void *)parsed_exeinfo);
return NULL;
}
for (; marker[0] == ' '; marker++);
if (!(marker = std::strchr(marker, ' ')))
{
xfree((void *)parsed_exeinfo->exe);
xfree((void *)parsed_exeinfo);
return NULL;
}
for (; marker[0] == ' '; marker++);
marker--;
marker[0] = '\0';
marker++;
exe = xstrdup(marker);
xfree((void *)parsed_exeinfo->exe);
parsed_exeinfo->exe = strreverse((char *)exe);
exeinfo = strreverse((char *)exeinfo);
std::sprintf(mask, "%%02u/%%02u/%%u %%02u:%%02u:%%02u %%u");
if (std::sscanf(exeinfo, mask, &t1.tm_mon, &t1.tm_mday, &t1.tm_year, &t1.tm_hour, &t1.tm_min, &t1.tm_sec, &size) != 7) {
if (std::sscanf(exeinfo, "%*s %*s %u", &size) != 1)
try
{
unsigned long versionid = std::strtoul(entry["versionbyte"].get<std::string>().c_str(), nullptr, 0);
if (versionid == ULONG_MAX || versionid == 0)
{
throw std::runtime_error("Invalid versionbyte \"" + entry["versionbyte"].get<std::string>() + "\" in entry \"" + entry["title"].get<std::string>() + "\"");
}
eventlog(eventlog_level_warn, __FUNCTION__, "parser error while parsing pattern \"{}\"", exeinfo);
xfree((void *)parsed_exeinfo->exe);
xfree((void *)parsed_exeinfo);
return NULL; /* neq */
unsigned long game_version;
if (verstr_to_vernum(entry["version"].get<std::string>().c_str(), &game_version) < 0)
{
throw std::runtime_error("Invalid version \"" + entry["version"].get<std::string>() + "\" in entry \"" + entry["title"].get<std::string>() + "\"");
}
unsigned long checksum = std::strtoul(entry["hash"].get<std::string>().c_str(), nullptr, 0);
if (checksum == ULONG_MAX || checksum == 0)
{
throw std::runtime_error("Invalid hash \"" + entry["hash"].get<std::string>() + "\" in entry \"" + entry["title"].get<std::string>() + "\"");
}
t_tag architecture = tag_str_to_uint(entry["architecture"].get<std::string>().c_str());
if (!tag_check_arch(architecture))
{
throw std::runtime_error("Invalid architecture \"" + entry["architecture"].get<std::string>() + "\" in entry \"" + entry["title"].get<std::string>() + "\"");
}
t_tag client = tag_str_to_uint(jclient.key().c_str());
if (!tag_check_client(client))
{
throw std::runtime_error("Invalid client \"" + entry["client"].get<std::string>() + "\" in entry \"" + entry["title"].get<std::string>() + "\"");
}
struct file_metadata metadata;
if (entry["file_metadata"].get<std::string>() == "NULL")
{
metadata = {};
}
else
{
metadata = parse_file_metadata(entry["file_metadata"].get<std::string>());
if (metadata.filename.empty()
&& metadata.file_size == 0
&& metadata.timestamp == 0)
{
throw std::runtime_error("Invalid file_metadata \"" + entry["file_metadata"].get<std::string>() + "\" in entry \"" + entry["title"].get<std::string>() + "\"");
}
}
// unable to check for errors at this time
std::string equation = entry["equation"].get<std::string>();
std::string checkrevision_filename = entry["checkrevision_file"].get<std::string>();
std::string version_tag = entry["versiontag"].get<std::string>();
vc_entries.push_front(VersionCheck(versionid, game_version, checksum, architecture, client,
metadata, equation, checkrevision_filename, version_tag));
success_count += 1;
}
time_invalid = 1;
}
/* Now we have a Y2K problem :) Thanks for using a 2 digit decimal years, Blizzard. */
/* 00-79 -> 2000-2079
* * 80-99 -> 1980-1999
* * 100+ unchanged */
if (t1.tm_year < 80)
t1.tm_year = t1.tm_year + 100;
if (time_invalid)
parsed_exeinfo->time = static_cast<std::time_t>(-1);
else
parsed_exeinfo->time = std::mktime(&t1);
parsed_exeinfo->size = size;
}
return parsed_exeinfo;
}
/* This implements some dumb kind of pattern matching. Any '?'
* signs in the pattern are treated as "don't care" signs. This
* means that it doesn't matter what's on this place in the match.
*/
//static int versioncheck_compare_exeinfo(char const * pattern, char const * match)
static int versioncheck_compare_exeinfo(t_parsed_exeinfo * pattern, t_parsed_exeinfo * match)
{
assert(pattern);
assert(match);
if (!strcasecmp(prefs_get_version_exeinfo_match(), "none"))
return 0; /* ignore exeinfo */
if (std::strlen(pattern->exe) != std::strlen(match->exe))
return 1; /* neq */
if (std::strcmp(prefs_get_version_exeinfo_match(), "exact") == 0) {
return strcasecmp(pattern->exe, match->exe);
}
else if (std::strcmp(prefs_get_version_exeinfo_match(), "exactcase") == 0) {
return std::strcmp(pattern->exe, match->exe);
}
else if (std::strcmp(prefs_get_version_exeinfo_match(), "wildcard") == 0) {
unsigned int i;
size_t pattern_exelen = std::strlen(pattern->exe);
for (i = 0; i < pattern_exelen; i++)
if ((pattern->exe[i] != '?') && /* out "don't care" sign */
(safe_toupper(pattern->exe[i]) != safe_toupper(match->exe[i])))
return 1; /* neq */
return 0; /* ok */
}
else if (std::strcmp(prefs_get_version_exeinfo_match(), "parse") == 0) {
if (strcasecmp(pattern->exe, match->exe) != 0)
{
eventlog(eventlog_level_trace, __FUNCTION__, "filename differs");
return 1; /* neq */
}
if (pattern->size != match->size)
{
eventlog(eventlog_level_trace, __FUNCTION__, "size differs");
return 1; /* neq */
}
if ((pattern->time != -1) && prefs_get_version_exeinfo_maxdiff() && (abs(pattern->time - match->time) > (signed)prefs_get_version_exeinfo_maxdiff()))
{
eventlog(eventlog_level_trace, __FUNCTION__, "time differs by {}", abs(pattern->time - match->time));
return 1;
}
return 0; /* ok */
}
else {
eventlog(eventlog_level_error, __FUNCTION__, "unknown version exeinfo match method \"{}\"", prefs_get_version_exeinfo_match());
return -1; /* neq/fail */
}
}
void free_parsed_exeinfo(t_parsed_exeinfo * parsed_exeinfo)
{
if (parsed_exeinfo)
{
if (parsed_exeinfo->exe)
xfree((void *)parsed_exeinfo->exe);
xfree((void *)parsed_exeinfo);
}
}
extern int versioncheck_validate(t_versioncheck * vc, t_tag archtag, t_tag clienttag, char const * exeinfo, unsigned long versionid, unsigned long gameversion, unsigned long checksum)
{
t_elem const * curr;
t_versioninfo * vi;
int badexe, badcs;
t_parsed_exeinfo * parsed_exeinfo;
if (!vc)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc");
return -1;
}
badexe = badcs = 0;
parsed_exeinfo = parse_exeinfo(exeinfo);
LIST_TRAVERSE_CONST(versioninfo_head, curr)
{
if (!(vi = (t_versioninfo*)elem_get_data(curr))) /* should not happen */
{
eventlog(eventlog_level_error, __FUNCTION__, "version list contains NULL item");
continue;
}
if (vi->archtag != archtag)
continue;
if (vi->clienttag != clienttag)
continue;
if (std::strcmp(vi->eqn, vc->eqn) != 0)
continue;
if (std::strcmp(vi->mpqfile, vc->mpqfile) != 0)
continue;
if (vi->versionid && vi->versionid != versionid)
continue;
if (vi->gameversion && vi->gameversion != gameversion)
continue;
if ((!(parsed_exeinfo)) || (vi->parsed_exeinfo && (versioncheck_compare_exeinfo(vi->parsed_exeinfo, parsed_exeinfo) != 0)))
{
/*
* Found an entry matching but the exeinfo doesn't match.
* We need to rember this because if no other matching versions are found
* we will return badversion.
*/
badexe = 1;
}
else
badexe = 0;
if (vi->checksum && vi->checksum != checksum)
{
/*
* Found an entry matching but the checksum doesn't match.
* We need to rember this because if no other matching versions are found
* we will return badversion.
*/
badcs = 1;
}
else
badcs = 0;
if (vc->versiontag)
xfree((void *)vc->versiontag);
vc->versiontag = xstrdup(vi->versiontag);
if (badexe || badcs)
continue;
/* Ok, version and checksum matches or exeinfo/checksum are disabled
* anyway we have found a complete match */
eventlog(eventlog_level_info, __FUNCTION__, "got a matching entry: {}", vc->versiontag);
free_parsed_exeinfo(parsed_exeinfo);
return 1;
}
if (badcs) /* A match was found but the checksum was different */
{
eventlog(eventlog_level_info, __FUNCTION__, "bad checksum, closest match is: {}", vc->versiontag);
free_parsed_exeinfo(parsed_exeinfo);
return -1;
}
if (badexe) /* A match was found but the exeinfo string was different */
{
eventlog(eventlog_level_info, __FUNCTION__, "bad exeinfo, closest match is: {}", vc->versiontag);
free_parsed_exeinfo(parsed_exeinfo);
return -1;
}
/* No match in list */
eventlog(eventlog_level_info, __FUNCTION__, "no match in list, setting to: {}", vc->versiontag);
free_parsed_exeinfo(parsed_exeinfo);
return 0;
}
extern int versioncheck_load(char const * filename)
{
std::FILE * fp;
unsigned int line;
unsigned int pos;
char * buff;
char * temp;
char const * eqn;
char const * mpqfile;
char const * archtag;
char const * clienttag;
char const * exeinfo;
char const * versionid;
char const * gameversion;
char const * checksum;
char const * versiontag;
t_versioninfo * vi;
if (!filename)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL filename");
return -1;
}
if (!(versioninfo_head = list_create()))
{
eventlog(eventlog_level_error, __FUNCTION__, "could create list");
return -1;
}
if (!(fp = std::fopen(filename, "r")))
{
eventlog(eventlog_level_error, __FUNCTION__, "could not open file \"{}\" for reading (std::fopen: {})", filename, std::strerror(errno));
list_destroy(versioninfo_head);
versioninfo_head = NULL;
return -1;
}
line = 1;
for (; (buff = file_get_line(fp)); line++)
{
for (pos = 0; buff[pos] == '\t' || buff[pos] == ' '; pos++);
if (buff[pos] == '\0' || buff[pos] == '#')
{
continue;
}
if ((temp = std::strrchr(buff, '#')))
{
unsigned int len;
unsigned int endpos;
*temp = '\0';
len = std::strlen(buff) + 1;
for (endpos = len - 1; buff[endpos] == '\t' || buff[endpos] == ' '; endpos--);
buff[endpos + 1] = '\0';
}
if (!(eqn = next_token(buff, &pos)))
{
eventlog(eventlog_level_error, __FUNCTION__, "missing eqn near line {} of file \"{}\"", line, filename);
continue;
}
line++;
if (!(mpqfile = next_token(buff, &pos)))
{
eventlog(eventlog_level_error, __FUNCTION__, "missing mpqfile near line {} of file \"{}\"", line, filename);
continue;
}
line++;
if (!(archtag = next_token(buff, &pos)))
{
eventlog(eventlog_level_error, __FUNCTION__, "missing archtag near line {} of file \"{}\"", line, filename);
continue;
}
line++;
if (!(clienttag = next_token(buff, &pos)))
{
eventlog(eventlog_level_error, __FUNCTION__, "missing clienttag near line {} of file \"{}\"", line, filename);
continue;
}
line++;
if (!(exeinfo = next_token(buff, &pos)))
{
eventlog(eventlog_level_error, __FUNCTION__, "missing exeinfo near line {} of file \"{}\"", line, filename);
continue;
}
line++;
if (!(versionid = next_token(buff, &pos)))
{
eventlog(eventlog_level_error, __FUNCTION__, "missing versionid near line {} of file \"{}\"", line, filename);
continue;
}
line++;
if (!(gameversion = next_token(buff, &pos)))
{
eventlog(eventlog_level_error, __FUNCTION__, "missing gameversion near line {} of file \"{}\"", line, filename);
continue;
}
line++;
if (!(checksum = next_token(buff, &pos)))
{
eventlog(eventlog_level_error, __FUNCTION__, "missing checksum near line {} of file \"{}\"", line, filename);
continue;
}
line++;
if (!(versiontag = next_token(buff, &pos)))
{
versiontag = NULL;
}
vi = (t_versioninfo*)xmalloc(sizeof(t_versioninfo));
vi->eqn = xstrdup(eqn);
vi->mpqfile = xstrdup(mpqfile);
if (std::strlen(archtag) != 4)
{
eventlog(eventlog_level_error, __FUNCTION__, "invalid arch tag on line {} of file \"{}\"", line, filename);
xfree((void *)vi->mpqfile); /* avoid warning */
xfree((void *)vi->eqn); /* avoid warning */
xfree(vi);
continue;
}
if (!tag_check_arch((vi->archtag = tag_str_to_uint(archtag))))
{
eventlog(eventlog_level_error, __FUNCTION__, "got unknown archtag \"{}\"", archtag);
xfree((void *)vi->mpqfile); /* avoid warning */
xfree((void *)vi->eqn); /* avoid warning */
xfree(vi);
continue;
}
if (std::strlen(clienttag) != 4)
{
eventlog(eventlog_level_error, __FUNCTION__, "invalid client tag on line {} of file \"{}\"", line, filename);
xfree((void *)vi->mpqfile); /* avoid warning */
xfree((void *)vi->eqn); /* avoid warning */
xfree(vi);
continue;
}
if (!tag_check_client((vi->clienttag = tag_str_to_uint(clienttag))))
{
eventlog(eventlog_level_error, __FUNCTION__, "got unknown clienttag\"{}\"", clienttag);
xfree((void *)vi->mpqfile); /* avoid warning */
xfree((void *)vi->eqn); /* avoid warning */
xfree(vi);
continue;
}
if (std::strcmp(exeinfo, "NULL") == 0)
vi->parsed_exeinfo = NULL;
else
{
if (!(vi->parsed_exeinfo = parse_exeinfo(exeinfo)))
catch (const std::exception& e)
{
eventlog(eventlog_level_error, __FUNCTION__, "encountered an error while parsing exeinfo");
xfree((void *)vi->mpqfile); /* avoid warning */
xfree((void *)vi->eqn); /* avoid warning */
xfree(vi);
eventlog(eventlog_level_error, __FUNCTION__, "{}", e.what());
continue;
}
}
vi->versionid = std::strtoul(versionid, NULL, 0);
if (verstr_to_vernum(gameversion, &vi->gameversion) < 0)
{
eventlog(eventlog_level_error, __FUNCTION__, "malformed version on line {} of file \"{}\"", line, filename);
xfree((void *)vi->parsed_exeinfo); /* avoid warning */
xfree((void *)vi->mpqfile); /* avoid warning */
xfree((void *)vi->eqn); /* avoid warning */
xfree(vi);
continue;
}
vi->checksum = std::strtoul(checksum, NULL, 0);
if (versiontag)
vi->versiontag = xstrdup(versiontag);
else
vi->versiontag = NULL;
list_append_data(versioninfo_head, vi);
}
file_get_line(NULL); // clear file_get_line buffer
if (std::fclose(fp) < 0)
eventlog(eventlog_level_error, __FUNCTION__, "could not close versioncheck file \"{}\" after reading (std::fclose: {})", filename, std::strerror(errno));
eventlog(eventlog_level_info, __FUNCTION__, "Successfully loaded {} out of {} versioncheck entries", success_count, total_count);
return 0;
versioncheck_conf_is_loaded = true;
return true;
}
extern int versioncheck_unload(void)
void unload_versioncheck_conf()
{
t_elem * curr;
t_versioninfo * vi;
if (versioninfo_head)
if (versioncheck_conf_is_loaded)
{
LIST_TRAVERSE(versioninfo_head, curr)
vc_entries.clear();
eventlog(eventlog_level_info, __FUNCTION__, "Successfully unloaded all version check entries");
versioncheck_conf_is_loaded = false;
}
}
const VersionCheck& select_versioncheck_entry(t_tag architecture, t_tag client, std::uint32_t version_id)
{
static const VersionCheck invalid_entry("IX86ver1.mpq", "A=42 B=42 C=42 4 A=A^S B=B^B C=C^C A=A^S", "NoVC");
for (const auto& entry : vc_entries)
{
if (entry.m_architecture == architecture
|| entry.m_client == client
|| entry.m_version_id == version_id)
{
if (!(vi = (t_versioninfo*)elem_get_data(curr))) /* should not happen */
{
eventlog(eventlog_level_error, __FUNCTION__, "version list contains NULL item");
continue;
}
if (list_remove_elem(versioninfo_head, &curr) < 0)
eventlog(eventlog_level_error, __FUNCTION__, "could not remove item from list");
if (vi->parsed_exeinfo)
{
if (vi->parsed_exeinfo->exe)
xfree((void *)vi->parsed_exeinfo->exe);
xfree((void *)vi->parsed_exeinfo); /* avoid warning */
}
xfree((void *)vi->mpqfile); /* avoid warning */
xfree((void *)vi->eqn); /* avoid warning */
if (vi->versiontag)
xfree((void *)vi->versiontag); /* avoid warning */
xfree(vi);
return entry;
}
if (list_destroy(versioninfo_head) < 0)
return -1;
versioninfo_head = NULL;
}
return 0;
return invalid_entry;
}
VersionCheck::VersionCheck(std::uint32_t version_id, std::uint32_t game_version, std::uint32_t checksum, t_tag architecture,
t_tag client, const struct file_metadata& metadata, const std::string& equation,
const std::string& checkrevision_filename, const std::string& version_tag)
: m_version_id(version_id),
m_game_version(game_version),
m_checksum(checksum),
m_architecture(architecture),
m_client(client),
m_metadata(metadata),
m_equation(equation),
m_checkrevision_filename(checkrevision_filename),
m_version_tag(version_tag)
{
}
VersionCheck::VersionCheck(const std::string& checkrevision_filename, const std::string& equation, const std::string& version_tag)
: m_version_id(),
m_game_version(),
m_checksum(),
m_architecture(),
m_client(),
m_metadata(),
m_equation(equation),
m_checkrevision_filename(checkrevision_filename),
m_version_tag(version_tag)
{
}
bool VersionCheck::validate_checkrevision_data(std::uint32_t game_version, std::uint32_t checksum, const std::string& unparsed_file_metadata) const
{
if (this->m_version_tag == "NoVC")
{
return false;
}
if (this->m_game_version != game_version)
{
eventlog(eventlog_level_debug, __FUNCTION__, "Failed CheckRevision: Invalid game version \"{X}\"", game_version);
return false;
}
if (this->m_checksum != 0
&& prefs_get_allow_bad_version())
{
if (this->m_checksum != checksum)
{
eventlog(eventlog_level_debug, __FUNCTION__, "Failed CheckRevision: Invalid checksum \"{X}\"", checksum);
return false;
}
}
else
{
// checksum can be disabled globally via setting allow_bad_version = true in conf/bnetd.conf
// it can also be disabled on individual versioncheck entries by setting 0 in the checksum field in conf/versioncheck.conf
eventlog(eventlog_level_debug, __FUNCTION__, "Skipping checksum validation");
}
if (unparsed_file_metadata != "NULL"
&& std::strcmp(prefs_get_version_exeinfo_match(), "true") == 0)
{
if (unparsed_file_metadata == "badexe")
{
// missing or too long file metadata received
eventlog(eventlog_level_debug, __FUNCTION__, "Failed CheckRevision: Invalid file metadata \"{}\"", unparsed_file_metadata);
return false;
}
struct file_metadata parsed_metadata = parse_file_metadata(unparsed_file_metadata);;
if (parsed_metadata.filename.empty()
&& parsed_metadata.file_size == 0
&& parsed_metadata.timestamp == 0)
{
eventlog(eventlog_level_debug, __FUNCTION__, "Failed CheckRevision: Invalid file metadata \"{}\"", unparsed_file_metadata);
return false;
}
// Skip timestamp matching if client is WC3
// WC3 installers change the file timestamp to time of installation
if (!compare_file_metadata(this->m_metadata, parsed_metadata,
this->m_client == CLIENTTAG_WARCRAFT3_UINT || this->m_client == CLIENTTAG_WAR3XP_UINT ? true : false)
)
{
eventlog(eventlog_level_debug, __FUNCTION__, "Failed CheckRevision: Invalid file metadata \"{}\"", unparsed_file_metadata);
return false;
}
}
else
{
// metadata matching can be disabled globally via setting version_exeinfo_match = false in conf/bnetd.conf
// it can also be disabled on individual versioncheck entries by setting "NULL" in the exeinfo field in conf/versioncheck.conf
eventlog(eventlog_level_debug, __FUNCTION__, "Skipping file metadata validation");
}
return true;
}
std::string VersionCheck::get_equation() const noexcept
{
return this->m_equation;
}
std::string VersionCheck::get_checkrevision_filename() const noexcept
{
return this->m_checkrevision_filename;
}
std::string VersionCheck::get_version_tag() const noexcept
{
return this->m_version_tag;
}
struct file_metadata parse_file_metadata(const std::string& unparsed_metadata)
{
// happens when using war3-noCD and having deleted war3.org
if (unparsed_metadata.empty())
{
eventlog(eventlog_level_error, __FUNCTION__, "got empty file metadata string");
return {};
}
// happens when AUTHREQ had no owner/exeinfo entry
if (unparsed_metadata == "badexe")
{
eventlog(eventlog_level_error, __FUNCTION__, "got \"badexe\" as file metadata string");
return {};
}
std::smatch tokens;
try
{
if (std::regex_match(unparsed_metadata, tokens, std::regex{ R"(([[:print:]]+\.exe) (\d\d/\d\d/\d\d \d\d:\d\d:\d\d) (\d+))" }) == false)
{
eventlog(eventlog_level_error, __FUNCTION__, "got invalid file metadata string \"{}\"", unparsed_metadata);
return {};
}
}
catch (const std::regex_error& e)
{
eventlog(eventlog_level_error, __FUNCTION__, "{} (code {})", e.what(), e.code());
return {};
}
// Example metadata string
// "Warcraft III.exe 07/07/17 20:15:59 562152"
std::string filename = tokens[1];
std::tm timestamp_raw;
timestamp_raw.tm_isdst = -1;
std::istringstream ss(tokens[2]);
// Since year is 2 digits,
// Range [69,99] results in values 1969 to 1999, range [00,68] results in 2000-2068
ss >> std::get_time(&timestamp_raw, "%D %T");
if (ss.fail())
{
eventlog(eventlog_level_error, __FUNCTION__, "got invalid date and time in file metadata string");
return {};
}
std::time_t timestamp = std::mktime(&timestamp_raw);
if (timestamp == -1)
{
eventlog(eventlog_level_error, __FUNCTION__, "time could not be represented as a std::time_t object");
return {};
}
std::uint64_t filesize;
try
{
filesize = std::stoull(tokens[3]);
}
catch (const std::exception& e)
{
eventlog(eventlog_level_error, __FUNCTION__, "could not convert file size in file metadata string: {}", e.what());
return {};
}
return { timestamp, filesize, filename };
}
bool compare_file_metadata(const struct file_metadata& pattern, const struct file_metadata& match, bool skip_timestamp_match)
{
if (skip_timestamp_match == false
&& pattern.timestamp != match.timestamp)
{
return false;
}
if (pattern.file_size != match.file_size)
{
return false;
}
// Case insensitive comparison of filename
if (strcasecmp(pattern.filename.c_str(), match.filename.c_str()) != 0)
{
return false;
}
return true;
}
}

View file

@ -18,84 +18,78 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef INCLUDED_VERSIONCHECK_TYPES
#define INCLUDED_VERSIONCHECK_TYPES
#ifndef PVPGN_BNETD_VERSIONCHECK_H
#define PVPGN_BNETD_VERSIONCHECK_H
#include <cstdint>
#include <ctime>
#include <string>
#include "common/tag.h"
namespace pvpgn
{
namespace bnetd
{
#ifdef VERSIONCHECK_INTERNAL_ACCESS
typedef struct
{
char const * exe;
std::time_t time;
int size;
} t_parsed_exeinfo;
#endif
#ifdef VERSIONCHECK_INTERNAL_ACCESS
typedef struct
{
char const * eqn;
char const * mpqfile;
t_tag archtag;
t_tag clienttag;
char const * versiontag;
t_parsed_exeinfo * parsed_exeinfo;
unsigned long versionid;
unsigned long gameversion;
unsigned long checksum;
} t_versioninfo;
#endif
typedef struct s_versioncheck
#ifdef VERSIONCHECK_INTERNAL_ACCESS
{
char const * eqn;
char const * mpqfile;
char const * versiontag;
}
#endif
t_versioncheck;
}
}
#endif
#ifndef JUST_NEED_TYPES
#ifndef INCLUDED_VERSIONCHECK_PROTOS
#define INCLUDED_VERSIONCHECK_PROTOS
namespace pvpgn
{
namespace bnetd
{
class VersionCheck;
extern t_versioncheck * versioncheck_create(t_tag archtag, t_tag clienttag);
extern int versioncheck_destroy(t_versioncheck * vc);
extern char const * versioncheck_get_mpqfile(t_versioncheck const * vc);
extern char const * versioncheck_get_eqn(t_versioncheck const * vc);
extern int versioncheck_validate(t_versioncheck * vc, t_tag archtag, t_tag clienttag, char const * exeinfo, unsigned long versionid, unsigned long gameversion, unsigned long checksum);
struct file_metadata
{
std::time_t timestamp;
std::uint64_t file_size;
std::string filename;
};
extern int versioncheck_load(char const * filename);
extern int versioncheck_unload(void);
bool load_versioncheck_conf(const std::string& filename);
void unload_versioncheck_conf();
extern char const * versioncheck_get_versiontag(t_versioncheck const * vc);
extern int versioncheck_set_versiontag(t_versioncheck * vc, char const * versiontag);
const VersionCheck& select_versioncheck_entry(t_tag architecture, t_tag client, std::uint32_t version_id);
}
class VersionCheck
{
public:
bool validate_checkrevision_data(std::uint32_t game_version, std::uint32_t checksum, const std::string& unparsed_file_metadata) const;
}
std::string get_equation() const noexcept;
std::string get_checkrevision_filename() const noexcept;
std::string get_version_tag() const noexcept;
#endif
#endif
~VersionCheck() = default;
private:
/*******************************************************************************/
// Friend functions
/*******************************************************************************/
friend bool load_versioncheck_conf(const std::string& filename);
friend const VersionCheck& select_versioncheck_entry(t_tag architecture, t_tag client, std::uint32_t version_id);
/*******************************************************************************/
// Constructors
/*******************************************************************************/
VersionCheck(std::uint32_t version_id, std::uint32_t game_version, std::uint32_t checksum, t_tag architecture,
t_tag client, const struct file_metadata& exe_metadata, const std::string& equation,
const std::string& checkrevision_filename, const std::string& version_tag);
VersionCheck(const std::string& checkrevision_filename, const std::string& equation, const std::string& version_tag);
/*******************************************************************************/
// Member variables
/*******************************************************************************/
const std::uint32_t m_version_id; // AKA "Version Byte"
const std::uint32_t m_game_version;
const std::uint32_t m_checksum;
const t_tag m_architecture;
const t_tag m_client;
const struct file_metadata m_metadata;
const std::string m_equation;
const std::string m_checkrevision_filename;
const std::string m_version_tag;
};
} // namespace bnetd
} // namespace pvpgn
#endif //PVPGN_BNETD_VERSIONCHECK_H

View file

@ -240,7 +240,7 @@ const unsigned BNETD_HASHTABLE_SIZE = 61;
const int BNETD_REALM_PORT = 6113; /* where D2CS listens */
const char * const BNETD_TELNET_ADDRS = ""; /* this means none */
const int BNETD_TELNET_PORT = 23; /* used if port not specified */
const char * const BNETD_EXEINFO_MATCH = "exact";
const char * const BNETD_EXEINFO_MATCH = "true";
const unsigned PVPGN_VERSION_TIMEDIV = 0; /* no timediff check by default */
const int PVPGN_CACHE_MEMLIMIT = 5000000; /* bytes */
const char * const PVPGN_DEFAULT_SYMB = "-_[]";