Overhaul versioncheck system (#340)

* Overhaul versioncheck system
 - Removed version_exeinfo_match, skip_versioncheck, and version_exeinfo_maxdiff from bnetd.conf
 - Modified configuration file to use JSON
 - Correctly finds the appropriate versioncheck entries
 - Add versioncheck.md to docs/

* Update JSON for Modern C++ from 3.0.1 to 3.1.2
This commit is contained in:
RElesgoe 2018-04-28 17:23:10 -07:00 committed by GitHub
parent 85f26eb2ab
commit 74b06d022b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 6590 additions and 5090 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"
@ -209,19 +209,6 @@ star_iconfile = "icons_STAR.bni"
# Example: allowed_clients = war3,w3xp
allowed_clients = all
# If this option is enabled, the verification step is skipped if possible.
# This only works with clients < 109. It is useful because you no longer
# need any of the IX86AUTH?.MPQ and PMACAUTH?.MPQ files. Note that it will
# also skip over all the autoupdate checks effectively disabling it.
#
# If you disable this you must have one or more of the MPQ files. Otherwise
# clients will hang when they first connect because they are attempting to
# download them. The versioncheck can only be skipped for clients older
# than 109. Starting with version 109 the clients will always do version
# checking since they do not function properly if the server does not
# request it.
skip_versioncheck = true
# If you enable the version checks but want to allow clients that don't pass
# the checksum test then enable this.
allow_bad_version = true
@ -232,19 +219,6 @@ 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
# #
##############################################################################
@ -448,7 +422,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

1855
conf/versioncheck.json.in Normal file

File diff suppressed because it is too large Load diff

16
docs/versioncheck.md Normal file
View file

@ -0,0 +1,16 @@
# VersionCheck
### Configuration File
The configuration file is in standard JSON format and consists of arrays for each Battle.net game client whose names are a four-letter string.
Within the game client arrays are arrays for platform whose names are also a four-letter string (i.e. `IX86`, `PMAC`, `XMAC`).
Within the platform arrays are arrays for the game client's version ID, traditionally known as a "version byte". The version ID is traditionally written in hex format, as indicated with a preceding "0x" or "0X", but can also be in decimal format. The version ID array consists of two pairs, `checkRevisionFile` and `equation`, and an array, `entries`.
The `entries` array consists of five pairs: `title`, `version`, `hash`, `fileMetadata`, `versionTag`.
- `title`: This is to assist the reader of the configuration file, it does not affect the entry in any way.
- `version`: The version number returned by CheckRevision which is obtained from the [VERSIONINFO](https://msdn.microsoft.com/en-us/library/aa381058) resource of the game file. See sample implementation: https://github.com/pvpgn/CheckRevision
- `hash`: The hash returned by CheckRevision which uses up to three files from the game to produce the hash. See sample implementation: https://github.com/pvpgn/CheckRevision
- `fileMetadata`: The string returned by CheckRevision which consists of the game's filename, last modified date, last modified time, and filesize, all separated by one space (e.g. `war3.exe 08/16/09 19:21:59 471040`). See sample implementation: https://github.com/pvpgn/CheckRevision
- Note: This pair is currently unused by PvPGN, but may be used in the future.
- `versionTag`: An arbitrary string that must be unique from all other version tags. It is traditionally in the form of the four-letter game string, followed by an underscore and the version (e.g. `WAR3_1282` is used for WarCraft 3: Reign of Chaos 1.28.2).

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) ? conn_get_versioncheck(c)->get_version_tag().c_str() : nullptr;
list_append_data(matchlists[queue][level], md);
@ -834,7 +828,11 @@ 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
&& conn_get_versioncheck(c)
&& !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

@ -4627,7 +4627,7 @@ namespace pvpgn
// disallow get/set value for password hash and username (hash can be cracked easily, account name should be permanent)
if (strcasecmp(key, "bnet\\acct\\passhash1") == 0 || strcasecmp(key, "bnet\\acct\\username") == 0 || strcasecmp(key, "bnet\\username") == 0)
if (strcasecmp(key, "bnet\\acct\\passhash1") == 0 || strcasecmp(key, "bnet\\acct\\username") == 0 || strcasecmp(key, "bnet\\acct\\verifier") == 0 || strcasecmp(key, "bnet\\acct\\salt") == 0 || strcasecmp(key, "bnet\\username") == 0)
{
message_send_text(c, message_type_info, c, localize(c, "Access denied due to security reasons."));
return -1;

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;
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,7 +29,9 @@
#include <cstdint>
#include <cstring>
#include <fstream>
#include <limits>
#include <sstream>
#include <tuple>
#include "compat/strcasecmp.h"
#include "compat/strncasecmp.h"
@ -570,15 +572,12 @@ namespace pvpgn
packet_del_ref(rpacket);
}
if ((rpacket = packet_create(packet_class_bnet))) {
t_versioncheck *vc;
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selecting version check", conn_get_socket(c));
vc = versioncheck_create(conn_get_archtag(c), conn_get_clienttag(c));
conn_set_versioncheck(c, vc);
if ((rpacket = packet_create(packet_class_bnet)))
{
packet_set_size(rpacket, sizeof(t_server_authreq_109));
packet_set_type(rpacket, SERVER_AUTHREQ_109);
// Logon type
if ((conn_get_clienttag(c) == CLIENTTAG_WARCRAFT3_UINT))
bn_int_set(&rpacket->u.server_authreq_109.logontype, SERVER_AUTHREQ_109_LOGONTYPE_W3);
else if ((conn_get_clienttag(c) == CLIENTTAG_WAR3XP_UINT))
@ -586,18 +585,29 @@ namespace pvpgn
else
bn_int_set(&rpacket->u.server_authreq_109.logontype, SERVER_AUTHREQ_109_LOGONTYPE);
// Session
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));
// CheckRevision
std::tuple<std::string, std::string> checkrevision = select_checkrevision(bn_int_get(packet->u.client_countryinfo_109.archtag), bn_int_get(packet->u.client_countryinfo_109.clienttag), bn_int_get(packet->u.client_countryinfo_109.versionid));
file_to_mod_time(c, std::get<0>(checkrevision).c_str(), &rpacket->u.server_authreq_109.timestamp); // Checkrevision file timestamp
packet_append_string(rpacket, std::get<0>(checkrevision).c_str()); // CheckRevision filename
packet_append_string(rpacket, std::get<1>(checkrevision).c_str()); // CheckRevision equation
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selected \"{}\" \"{}\"", conn_get_socket(c), std::get<0>(checkrevision), std::get<1>(checkrevision));
// WarCraft 3 Server Signature
if ((conn_get_clienttag(c) == CLIENTTAG_WARCRAFT3_UINT)
|| (conn_get_clienttag(c) == CLIENTTAG_WAR3XP_UINT)) {
|| (conn_get_clienttag(c) == CLIENTTAG_WAR3XP_UINT))
{
char padding[128];
std::memset(padding, 0, 128);
packet_append_data(rpacket, padding, 128);
}
conn_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
}
@ -633,36 +643,23 @@ namespace pvpgn
conn_set_archtag(c, bn_int_get(packet->u.client_progident.archtag));
conn_set_clienttag(c, bn_int_get(packet->u.client_progident.clienttag));
if (prefs_get_skip_versioncheck()) {
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] attempting to skip version check by sending early authreply", conn_get_socket(c));
/* skip over SERVER_AUTHREQ1 and CLIENT_AUTHREQ1 */
if ((rpacket = packet_create(packet_class_bnet))) {
packet_set_size(rpacket, sizeof(t_server_authreply1));
packet_set_type(rpacket, SERVER_AUTHREPLY1);
bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_OK);
packet_append_string(rpacket, "");
packet_append_string(rpacket, ""); /* FIXME: what's the second string for? */
conn_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
}
}
else {
t_versioncheck *vc;
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selecting version check", conn_get_socket(c));
vc = versioncheck_create(conn_get_archtag(c), conn_get_clienttag(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));
// CheckRevision
std::tuple<std::string, std::string> checkrevision = select_checkrevision(bn_int_get(packet->u.client_progident.archtag), bn_int_get(packet->u.client_progident.clienttag), bn_int_get(packet->u.client_progident.versionid));
file_to_mod_time(c, std::get<0>(checkrevision).c_str(), &rpacket->u.server_authreq_109.timestamp); // Checkrevision file timestamp
packet_append_string(rpacket, std::get<0>(checkrevision).c_str()); // CheckRevision filename
packet_append_string(rpacket, std::get<1>(checkrevision).c_str()); // CheckRevision equation
eventlog(eventlog_level_debug, __FUNCTION__, "[{}] selected \"{}\" \"{}\"", conn_get_socket(c), std::get<0>(checkrevision), std::get<1>(checkrevision));
conn_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
}
}
return 0;
}
@ -1000,212 +997,245 @@ 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;
}
auto send_failed_packet = [](t_connection *c)
{
char verstr[16];
char const *exeinfo;
char const *versiontag;
int failed;
t_packet *rpacket = packet_create(packet_class_bnet);
if (rpacket)
{
packet_set_size(rpacket, sizeof(t_server_authreply1));
packet_set_type(rpacket, SERVER_AUTHREPLY1);
conn_set_state(c, conn_state_untrusted);
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;
bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_BADVERSION);
packet_append_string(rpacket, "");
packet_append_string(rpacket, ""); // undocumented extra null terminator
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_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
}
};
// 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))
{
send_failed_packet(c);
return 0;
}
if (bn_int_get(packet->u.client_authreq1.clienttag) != conn_get_clienttag(c))
{
send_failed_packet(c);
return 0;
}
if (bn_int_get(packet->u.client_authreq1.versionid) != conn_get_versionid(c))
{
send_failed_packet(c);
return 0;
}
const char *exeinfo = packet_get_str_const(packet, sizeof(t_client_authreq1), MAX_EXEINFO_STR);
if (exeinfo)
{
conn_set_clientexe(c, exeinfo);
}
else
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ1 (missing or too long exeinfo)", conn_get_socket(c));
send_failed_packet(c);
return 0;
}
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, 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));
std::strcpy(verstr, vernum_to_verstr(bn_int_get(packet->u.client_authreq1.gameversion)));
conn_set_clientver(c, verstr);
conn_set_clientexe(c, exeinfo);
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));
if ((rpacket = packet_create(packet_class_bnet))) {
const VersionCheck* vc = select_versioncheck(conn_get_archtag(c), conn_get_clienttag(c), conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c));
conn_set_versioncheck(c, vc);
if (vc)
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client matches versiontag \"{}\"", conn_get_socket(c), conn_get_versioncheck(c)->get_version_tag());
}
else
{
if (prefs_get_allow_unknown_version())
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] skipping versioncheck because allow_unknown_version is true", conn_get_socket(c));
}
else
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] client failed versioncheck", conn_get_socket(c));
send_failed_packet(c);
return 0;
}
}
t_packet *rpacket = packet_create(packet_class_bnet);
if (rpacket)
{
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));
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 */
char *mpqfilename = nullptr;
if (vc)
{
mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), vc->get_version_tag().c_str(), nullptr);
}
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);
// Only handle updates when there is an update file available.
if (mpqfilename)
{
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), versioncheck_get_versiontag(conn_get_versioncheck(c)));
else
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] no upgrade is available", conn_get_socket(c));
}
bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_OK);
packet_append_string(rpacket, "");
}
packet_append_string(rpacket, ""); // FIXME: what's the second string for?
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);
}
}
return 0;
}
static int _client_authreq109(t_connection * c, t_packet const *const packet)
{
t_packet *rpacket;
if (packet_get_size(packet) < sizeof(t_client_authreq_109)) {
if (packet_get_size(packet) < sizeof(t_client_authreq_109))
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 packet (expected {} bytes, got {})", conn_get_socket(c), sizeof(t_client_authreq_109), packet_get_size(packet));
return 0;
}
auto send_failed_packet = [](t_connection *c)
{
char verstr[16];
char const *exeinfo;
char const *versiontag;
int failed;
char const *owner;
unsigned int count;
unsigned int pos;
t_packet *rpacket = packet_create(packet_class_bnet);
if (rpacket)
{
packet_set_size(rpacket, sizeof(t_server_authreply_109));
packet_set_type(rpacket, SERVER_AUTHREPLY_109);
conn_set_state(c, conn_state_untrusted);
failed = 0;
count = bn_int_get(packet->u.client_authreq_109.cdkey_number);
pos = sizeof(t_client_authreq_109)+(count * sizeof(t_cdkey_info));
bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_BADVERSION);
packet_append_string(rpacket, '\0');
if (!(exeinfo = packet_get_str_const(packet, pos, MAX_EXEINFO_STR))) {
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 (missing or too long exeinfo)", conn_get_socket(c));
exeinfo = "badexe";
failed = 1;
conn_push_outqueue(c, rpacket);
packet_del_ref(rpacket);
}
};
std::uint32_t count = bn_int_get(packet->u.client_authreq_109.cdkey_number);
std::size_t position = sizeof(t_client_authreq_109) + (count * sizeof(t_cdkey_info));
const char *const exeinfo = packet_get_str_const(packet, position, MAX_EXEINFO_STR);
if (exeinfo)
{
conn_set_clientexe(c, exeinfo);
pos += std::strlen(exeinfo) + 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 */
}
else
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 (missing or too long exeinfo)", conn_get_socket(c));
send_failed_packet(c);
return 0;
}
position += std::strlen(exeinfo) + 1;
const char *const owner = packet_get_str_const(packet, position, MAX_OWNER_STR);
if (owner)
{
conn_set_owner(c, owner);
}
else
{
eventlog(eventlog_level_error, __FUNCTION__, "[{}] got bad AUTHREQ_109 (missing or too long owner)", conn_get_socket(c));
conn_set_owner(c, "");
}
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));
std::strcpy(verstr, vernum_to_verstr(bn_int_get(packet->u.client_authreq_109.gameversion)));
conn_set_clientver(c, verstr);
conn_set_clientexe(c, exeinfo);
std::string version = vernum_to_verstr(bn_int_get(packet->u.client_authreq_109.gameversion));
conn_set_clientver(c, version.c_str());
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), version, exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c));
if ((rpacket = packet_create(packet_class_bnet))) {
t_packet *rpacket;
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
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;
const VersionCheck* vc = select_versioncheck(conn_get_archtag(c), conn_get_clienttag(c), conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c));
conn_set_versioncheck(c, vc);
if (vc)
{
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;
{
if (prefs_get_allow_unknown_version())
{
eventlog(eventlog_level_info, __FUNCTION__, "[{}] skipping versioncheck because allow_unknown_version is true", conn_get_socket(c));
}
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));
send_failed_packet(c);
return 0;
}
}
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_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);
/* 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);
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);
if (vc)
{
// Only handle updates when there is an update file available.
if (mpqfilename)
{
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(static_cast<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 is available", conn_get_socket(c));
}
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);
packet_del_ref(rpacket);
}
}
return 0;
}
@ -2427,97 +2457,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;
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);
}
}
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)))
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;
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);
}
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(friend_connection) ? 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;
}
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(user_connection) ? 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 (!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);
if (available_players == 0)
{
eventlog(eventlog_level_info, __FUNCTION__, "No available players");
}
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 +3749,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 +4812,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

@ -213,11 +213,8 @@ namespace pvpgn
config.update("star_iconfile", prefs_get_star_iconfile());
config.update("tosfile", prefs_get_tosfile());
config.update("allowed_clients", prefs_get_allowed_clients());
config.update("skip_versioncheck", prefs_get_skip_versioncheck());
config.update("allow_bad_version", prefs_get_allow_bad_version());
config.update("allow_unknown_version", prefs_get_allow_unknown_version());
config.update("version_exeinfo_match", prefs_get_version_exeinfo_match());
config.update("version_exeinfo_maxdiff", prefs_get_version_exeinfo_maxdiff());
config.update("usersync", prefs_get_user_sync_timer());
config.update("userflush", prefs_get_user_flush_timer());
config.update("userflush_connected", prefs_get_user_flush_connected());

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

@ -118,7 +118,6 @@ namespace pvpgn
char const * maildir;
char const * log_notice;
unsigned int savebyname;
unsigned int skip_versioncheck;
unsigned int allow_bad_version;
unsigned int allow_unknown_version;
char const * versioncheck_file;
@ -494,10 +493,6 @@ namespace pvpgn
static const char *conf_get_savebyname(void);
static int conf_setdef_savebyname(void);
static int conf_set_skip_versioncheck(const char *valstr);
static const char *conf_get_skip_versioncheck(void);
static int conf_setdef_skip_versioncheck(void);
static int conf_set_allow_bad_version(const char *valstr);
static const char *conf_get_allow_bad_version(void);
static int conf_setdef_allow_bad_version(void);
@ -530,14 +525,6 @@ namespace pvpgn
static const char *conf_get_ipban_check_int(void);
static int conf_setdef_ipban_check_int(void);
static int conf_set_version_exeinfo_match(const char *valstr);
static const char *conf_get_version_exeinfo_match(void);
static int conf_setdef_version_exeinfo_match(void);
static int conf_set_version_exeinfo_maxdiff(const char *valstr);
static const char *conf_get_version_exeinfo_maxdiff(void);
static int conf_setdef_version_exeinfo_maxdiff(void);
static int conf_set_max_concurrent_logins(const char *valstr);
static const char *conf_get_max_concurrent_logins(void);
static int conf_setdef_max_concurrent_logins(void);
@ -816,7 +803,6 @@ namespace pvpgn
{ "maildir", conf_set_maildir, conf_get_maildir, conf_setdef_maildir },
{ "log_notice", conf_set_log_notice, conf_get_log_notice, conf_setdef_log_notice },
{ "savebyname", conf_set_savebyname, conf_get_savebyname, conf_setdef_savebyname },
{ "skip_versioncheck", conf_set_skip_versioncheck, conf_get_skip_versioncheck, conf_setdef_skip_versioncheck },
{ "allow_bad_version", conf_set_allow_bad_version, conf_get_allow_bad_version, conf_setdef_allow_bad_version },
{ "allow_unknown_version", conf_set_allow_unknown_version, conf_get_allow_unknown_version, conf_setdef_allow_unknown_version },
{ "versioncheck_file", conf_set_versioncheck_file, conf_get_versioncheck_file, conf_setdef_versioncheck_file },
@ -825,8 +811,6 @@ namespace pvpgn
{ "hashtable_size", conf_set_hashtable_size, conf_get_hashtable_size, conf_setdef_hashtable_size },
{ "telnetaddrs", conf_set_telnetaddrs, conf_get_telnetaddrs, conf_setdef_telnetaddrs },
{ "ipban_check_int", conf_set_ipban_check_int, conf_get_ipban_check_int, conf_setdef_ipban_check_int },
{ "version_exeinfo_match", conf_set_version_exeinfo_match, conf_get_version_exeinfo_match, conf_setdef_version_exeinfo_match },
{ "version_exeinfo_maxdiff", conf_set_version_exeinfo_maxdiff, conf_get_version_exeinfo_maxdiff, conf_setdef_version_exeinfo_maxdiff },
{ "max_concurrent_logins", conf_set_max_concurrent_logins, conf_get_max_concurrent_logins, conf_setdef_max_concurrent_logins },
{ "mapsfile", conf_set_mapsfile, conf_get_mapsfile, conf_setdef_mapsfile },
{ "xplevelfile", conf_set_xplevelfile, conf_get_xplevelfile, conf_setdef_xplevelfile },
@ -2564,27 +2548,6 @@ namespace pvpgn
}
extern unsigned int prefs_get_skip_versioncheck(void)
{
return prefs_runtime_config.skip_versioncheck;
}
static int conf_set_skip_versioncheck(const char *valstr)
{
return conf_set_bool(&prefs_runtime_config.skip_versioncheck, valstr, 0);
}
static int conf_setdef_skip_versioncheck(void)
{
return conf_set_bool(&prefs_runtime_config.skip_versioncheck, NULL, 0);
}
static const char* conf_get_skip_versioncheck(void)
{
return conf_get_bool(prefs_runtime_config.skip_versioncheck);
}
extern unsigned int prefs_get_allow_bad_version(void)
{
return prefs_runtime_config.allow_bad_version;
@ -2753,48 +2716,6 @@ namespace pvpgn
}
extern char const * prefs_get_version_exeinfo_match(void)
{
return prefs_runtime_config.version_exeinfo_match;
}
static int conf_set_version_exeinfo_match(const char *valstr)
{
return conf_set_str(&prefs_runtime_config.version_exeinfo_match, valstr, NULL);
}
static int conf_setdef_version_exeinfo_match(void)
{
return conf_set_str(&prefs_runtime_config.version_exeinfo_match, NULL, BNETD_EXEINFO_MATCH);
}
static const char* conf_get_version_exeinfo_match(void)
{
return prefs_runtime_config.version_exeinfo_match;
}
extern unsigned int prefs_get_version_exeinfo_maxdiff(void)
{
return prefs_runtime_config.version_exeinfo_maxdiff;
}
static int conf_set_version_exeinfo_maxdiff(const char *valstr)
{
return conf_set_int(&prefs_runtime_config.version_exeinfo_maxdiff, valstr, 0);
}
static int conf_setdef_version_exeinfo_maxdiff(void)
{
return conf_set_int(&prefs_runtime_config.version_exeinfo_maxdiff, NULL, PVPGN_VERSION_TIMEDIV);
}
static const char* conf_get_version_exeinfo_maxdiff(void)
{
return conf_get_int(prefs_runtime_config.version_exeinfo_maxdiff);
}
extern unsigned int prefs_get_max_concurrent_logins(void)
{
return prefs_runtime_config.max_concurrent_logins;

View file

@ -113,7 +113,6 @@ namespace pvpgn
extern char const * prefs_get_maildir(void);
extern char const * prefs_get_log_notice(void);
extern unsigned int prefs_get_savebyname(void);
extern unsigned int prefs_get_skip_versioncheck(void);
extern unsigned int prefs_get_allow_bad_version(void);
extern unsigned int prefs_get_allow_unknown_version(void);
extern char const * prefs_get_versioncheck_file(void);
@ -122,8 +121,6 @@ namespace pvpgn
extern unsigned int prefs_get_hashtable_size(void);
extern char const * prefs_get_telnet_addrs(void);
extern unsigned int prefs_get_ipban_check_int(void);
extern char const * prefs_get_version_exeinfo_match(void);
extern unsigned int prefs_get_version_exeinfo_maxdiff(void);
extern unsigned int prefs_get_max_concurrent_logins(void);

View file

@ -1415,10 +1415,12 @@ 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

@ -16,645 +16,262 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "common/setup_before.h"
#define VERSIONCHECK_INTERNAL_ACCESS
#include "versioncheck.h"
#include <ctime>
#include <cstring>
#include <cstdio>
#include <cassert>
#include <cctype>
#include <cerrno>
#include <chrono>
#include <climits>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <regex>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include "compat/strcasecmp.h"
#include "common/list.h"
#include "common/xalloc.h"
#include "common/eventlog.h"
#include "common/util.h"
#include "common/field_sizes.h"
#include "common/token.h"
#include "common/format.h"
#include "common/hash_tuple.hpp"
#include "common/proginfo.h"
#include "common/xstring.h"
#include "compat/strcasecmp.h"
#include "common/token.h"
#include "common/util.h"
#include "json/json.hpp"
#include "prefs.h"
#include "common/setup_after.h"
using json = nlohmann::json;
namespace pvpgn
{
namespace bnetd
{
std::unordered_map<std::tuple<std::uint32_t, std::uint32_t, t_tag, t_tag>, VersionCheck, hash_tuple::hash<std::tuple<std::uint32_t, std::uint32_t, t_tag, t_tag>>> vc_entries;
std::unordered_map<std::tuple<t_tag, t_tag, std::uint32_t>, std::tuple<std::string, std::string>, hash_tuple::hash<std::tuple<t_tag, t_tag, std::uint32_t>>> cr_entries;
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" };
bool versioncheck_conf_is_loaded = false;
static int versioncheck_compare_exeinfo(t_parsed_exeinfo * pattern, t_parsed_exeinfo * match);
// bool validate_file_metadata_format(const std::string& metadata);
extern t_versioncheck * versioncheck_create(t_tag archtag, t_tag clienttag)
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];
if (versioncheck_conf_is_loaded)
{
eventlog(eventlog_level_error, __FUNCTION__, "Could not load {}, a versioncheck configuration file is already loaded", filename);
return false;
}
LIST_TRAVERSE_CONST(versioninfo_head, curr)
auto t0 = std::chrono::steady_clock::now();
std::ifstream file_stream(filename, std::ios::in);
if (!file_stream.is_open())
{
if (!(vi = (t_versioninfo*)elem_get_data(curr))) /* should not happen */
eventlog(eventlog_level_error, __FUNCTION__, "Could not open file \"{}\" for reading", filename);
return false;
}
json jconf;
try
{
eventlog(eventlog_level_error, __FUNCTION__, "version list contains NULL item");
file_stream >> jconf;
}
catch (const std::exception& e)
{
eventlog(eventlog_level_error, __FUNCTION__, "Could not parse {}: {}", filename, e.what());
return false;
}
vc_entries.reserve(185);
for (auto jclient : jconf.items())
{
t_tag client = tag_str_to_uint(jclient.key().c_str());
if (!tag_check_client(client))
{
eventlog(eventlog_level_error, __FUNCTION__, "Invalid client: {}", jclient.key());
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)
for (auto jarchitecture : jclient.value().items())
{
t_tag architecture = tag_str_to_uint(jarchitecture.key().c_str());
if (!tag_check_arch(architecture))
{
eventlog(eventlog_level_error, __FUNCTION__, "Invalid architecture: {}", jarchitecture.key());
continue;
if (vi->clienttag != clienttag)
}
for (auto jversion_id : jarchitecture.value().items())
{
std::uint32_t version_id;
try
{
version_id = std::stoul(jversion_id.key(), nullptr, 0);
}
catch (const std::exception& e)
{
eventlog(eventlog_level_error, __FUNCTION__, "Invalid version id \"{}\": {}", jversion_id.key(), e.what());
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));
// FIXME: do error checking for these
std::string checkrevision_filename = jversion_id.value()["checkRevisionFile"].get<std::string>();
std::string checkrevision_equation = jversion_id.value()["equation"].get<std::string>();
return vc;
cr_entries.emplace(
std::piecewise_construct,
std::forward_as_tuple(architecture, client, version_id),
std::forward_as_tuple(checkrevision_filename, checkrevision_equation)
);
for (auto jentry : jversion_id.value()["entries"])
{
try
{
VersionCheck entry(jentry["title"].get<std::string>(),
version_id,
jentry["version"].get<std::string>(),
jentry["hash"].get<std::string>(),
architecture,
client,
jentry["versionTag"].get<std::string>()
);
vc_entries.insert({ std::make_tuple(entry.m_version_id, entry.m_game_version, entry.m_architecture, entry.m_client), entry });
}
catch (const std::exception& e)
{
eventlog(eventlog_level_error, __FUNCTION__, "Failed to load versioncheck entry: {}", e.what());
continue;
}
}
}
}
}
auto t1 = std::chrono::steady_clock::now();
eventlog(eventlog_level_info, __FUNCTION__, "Successfully loaded {} versioncheck entries in {} microseconds", vc_entries.size(), std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count());
versioncheck_conf_is_loaded = true;
return true;
}
void unload_versioncheck_conf()
{
if (versioncheck_conf_is_loaded)
{
vc_entries.clear();
cr_entries.clear();
versioncheck_conf_is_loaded = false;
eventlog(eventlog_level_info, __FUNCTION__, "Successfully unloaded all version check entries");
}
}
std::tuple<std::string, std::string> select_checkrevision(t_tag architecture, t_tag client, std::uint32_t version_id)
{
static const std::tuple<std::string, std::string> default_checkrevision { "ver-IX86-1.mpq", "A=42 B=42 C=42 4 A=A^S B=B^B C=C^C A=A^S" };
auto key = cr_entries.find(std::make_tuple(architecture, client, version_id));
if (key != cr_entries.end())
{
return key->second;
}
eventlog(eventlog_level_debug, __FUNCTION__, "Could not find corresponding CheckRevision entry, returning default CheckRevision");
return default_checkrevision;
}
// could use C++17 std::optional here
const VersionCheck* select_versioncheck(t_tag architecture, t_tag client, std::uint32_t version_id,
std::uint32_t checkrevision_version, std::uint32_t checkrevision_checksum)
{
auto it = vc_entries.find(std::make_tuple(version_id, checkrevision_version, architecture, client));
if (it == vc_entries.end())
{
return nullptr;
}
if (it->second.m_checksum != checkrevision_checksum
&& !prefs_get_allow_bad_version())
{
return nullptr;
}
return &(it->second);
}
VersionCheck::VersionCheck(const std::string& title, std::uint32_t version_id, const std::string& game_version,
const std::string& checksum, t_tag architecture, t_tag client, const std::string& version_tag)
: m_version_id(version_id), m_architecture(architecture), m_client(client)
{
if (verstr_to_vernum(game_version.c_str(), reinterpret_cast<unsigned long *>(&this->m_game_version)) < 0)
{
throw std::runtime_error("Invalid version \"" + game_version + "\" in entry \"" + title + "\"");
}
this->m_checksum = std::stoul(checksum, nullptr, 0);
// FIXME: check for uniqueness
this->m_version_tag = version_tag;
}
std::string VersionCheck::get_version_tag() const
{
return this->m_version_tag;
}
/*
* 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.
// This function only validates the format of the metadata string
// There is no need to validate the information in the string because validating the checksum and file version is sufficient
bool validate_file_metadata_format(const std::string& metadata)
{
if (metadata.empty())
{
return false;
}
try
{
std::smatch tokens;
if (std::regex_match(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 \"{}\"", metadata);
return false;
}
}
catch (const std::regex_error& e)
{
eventlog(eventlog_level_error, __FUNCTION__, "{} (code {})", e.what(), e.code());
return false;
}
return true;
}
*/
return &dummyvc;
}
extern int versioncheck_destroy(t_versioncheck * vc)
{
if (!vc)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc");
return -1;
}
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)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc");
return NULL;
}
return vc->mpqfile;
}
extern char const * versioncheck_get_eqn(t_versioncheck const * vc)
{
if (!vc)
{
eventlog(eventlog_level_error, __FUNCTION__, "got NULL vc");
return NULL;
}
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)
{
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
{
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)
{
eventlog(eventlog_level_warn, __FUNCTION__, "parser error while parsing pattern \"{}\"", exeinfo);
xfree((void *)parsed_exeinfo->exe);
xfree((void *)parsed_exeinfo);
return NULL; /* neq */
}
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)))
{
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);
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));
return 0;
}
extern int versioncheck_unload(void)
{
t_elem * curr;
t_versioninfo * vi;
if (versioninfo_head)
{
LIST_TRAVERSE(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 (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);
}
if (list_destroy(versioninfo_head) < 0)
return -1;
versioninfo_head = NULL;
}
return 0;
}
}

View file

@ -16,86 +16,81 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 <tuple>
#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);
extern int versioncheck_load(char const * filename);
extern int versioncheck_unload(void);
// filename, equation
std::tuple<std::string, std::string> select_checkrevision(t_tag architecture, t_tag client, std::uint32_t version_id);
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(t_tag architecture, t_tag client, std::uint32_t version_id,
std::uint32_t checkrevision_version, std::uint32_t checkrevision_checksum);
}
}
/*******************************************************************************/
// Conf
/*******************************************************************************/
bool load_versioncheck_conf(const std::string& filename);
void unload_versioncheck_conf();
#endif
#endif
class VersionCheck
{
public:
/*******************************************************************************/
// Getters
/*******************************************************************************/
std::string get_version_tag() const;
/*******************************************************************************/
// Deconstructors
/*******************************************************************************/
~VersionCheck() = default;
private:
/*******************************************************************************/
// Friend functions
/*******************************************************************************/
friend bool load_versioncheck_conf(const std::string& filename);
friend const VersionCheck* select_versioncheck(t_tag architecture, t_tag client, std::uint32_t version_id,
std::uint32_t checkrevision_version, std::uint32_t checkrevision_checksum);
/*******************************************************************************/
// Constructors
/*******************************************************************************/
VersionCheck(const std::string& title, std::uint32_t version_id, const std::string& game_version,
const std::string& checksum, t_tag architecture, t_tag client, const std::string& version_tag);
/*******************************************************************************/
// Member variables
/*******************************************************************************/
std::uint32_t m_version_id; // AKA "Version Byte"
std::uint32_t m_game_version; // Windows file version
std::uint32_t m_checksum;
t_tag m_architecture;
t_tag m_client;
std::string m_version_tag;
};
} // namespace bnetd
} // namespace pvpgn
#endif //PVPGN_BNETD_VERSIONCHECK_H

View file

@ -10,7 +10,7 @@ set(COMMON_SOURCES
fdwatch_poll.h fdwatch_select.cpp fdwatch_select.h fdwbackend.cpp
fdwbackend.h field_sizes.h file_protocol.h flags.h
give_up_root_privileges.cpp give_up_root_privileges.h hashtable.cpp
hashtable.h hexdump.cpp hexdump.h init_protocol.h introtate.h
hashtable.h hash_tuple.hpp hexdump.cpp hexdump.h init_protocol.h introtate.h
irc_protocol.h list.cpp list.h lstr.h make_unique.hpp network.cpp network.h
packet.cpp packet.h proginfo.cpp proginfo.h queue.cpp queue.h rcm.cpp rcm.h
rlimit.cpp rlimit.h scoped_array.h scoped_ptr.h setup_after.h

67
src/common/hash_tuple.hpp Normal file
View file

@ -0,0 +1,67 @@
#ifndef INCLUDED_PVPGN_HASH_TUPLE_H
#define INCLUDED_PVPGN_HASH_TUPLE_H
#include <cstddef>
#include <functional>
#include <tuple>
// https://stackoverflow.com/questions/7110301/generic-hash-for-tuples-in-unordered-map-unordered-set
namespace pvpgn
{
namespace hash_tuple
{
template <typename TT>
struct hash
{
std::size_t operator()(TT const& tt) const
{
return std::hash<TT>()(tt);
}
};
namespace
{
template <class T>
inline void hash_combine(std::size_t& seed, T const& v)
{
seed ^= hash_tuple::hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
}
namespace
{
// Recursive template code derived from Matthieu M.
template <class Tuple, std::size_t Index = std::tuple_size<Tuple>::value - 1>
struct HashValueImpl
{
static void apply(std::size_t& seed, Tuple const& tuple)
{
HashValueImpl<Tuple, Index - 1>::apply(seed, tuple);
hash_combine(seed, std::get<Index>(tuple));
}
};
template <class Tuple>
struct HashValueImpl<Tuple, 0>
{
static void apply(std::size_t& seed, Tuple const& tuple)
{
hash_combine(seed, std::get<0>(tuple));
}
};
}
template <typename ... TT>
struct hash<std::tuple<TT...>>
{
std::size_t operator()(std::tuple<TT...> const& tt) const
{
std::size_t seed = 0;
HashValueImpl<std::tuple<TT...> >::apply(seed, tt);
return seed;
}
};
}
}
#endif

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 = "-_[]";

File diff suppressed because it is too large Load diff