-
-
Save RElesgoe/adea8f20cef0f5d5480b94cf59ba1e28 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2000 Onlyer (onlyer@263.net) | |
* Copyright (C) 2001 Ross Combs (ross@bnetd.org) | |
* Copyright (C) 2002 Gianluigi Tiesi (sherpya@netfarm.it) | |
* Copyright (C) 2004 CreepLord (creeplord@pvpgn.org) | |
* | |
* This program is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU General Public License | |
* as published by the Free Software Foundation; either version 2 | |
* of the License, or (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program; if not, write to the Free Software | |
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
*/ | |
#include "common/setup_before.h" | |
#define VERSIONCHECK_INTERNAL_ACCESS | |
#include "versioncheck.h" | |
#include <cctype> | |
#include <cerrno> | |
#include <climits> | |
#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/format.h" | |
#include "common/eventlog.h" | |
#include "common/util.h" | |
#include "common/field_sizes.h" | |
#include "common/token.h" | |
#include "common/proginfo.h" | |
#include "json/json.hpp" | |
#include "prefs.h" | |
#include "common/setup_after.h" | |
using json = nlohmann::json; | |
namespace pvpgn | |
{ | |
namespace bnetd | |
{ | |
std::forward_list<VersionCheck> vc_entries = std::forward_list<VersionCheck>(); | |
bool versioncheck_conf_is_loaded = false; | |
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) | |
{ | |
if (versioncheck_conf_is_loaded) | |
{ | |
eventlog(eventlog_level_error, __FUNCTION__, "Could not load {}, a versioncheck configuration file is already loaded", filename); | |
return false; | |
} | |
std::ifstream file_stream(filename, std::ios::in); | |
if (!file_stream.is_open()) | |
{ | |
eventlog(eventlog_level_error, __FUNCTION__, "Could not open file \"{}\" for reading", filename); | |
return false; | |
} | |
json jconf; | |
try | |
{ | |
file_stream >> jconf; | |
} | |
catch (const std::exception& e) | |
{ | |
eventlog(eventlog_level_error, __FUNCTION__, "Could not parse {}: {}", filename, e.what()); | |
return false; | |
} | |
int entry_success_count = 0; | |
int entry_total_count = 0; | |
for (auto jclient : jconf.items()) | |
{ | |
for (auto jarchitecture : jclient.value().items()) | |
{ | |
for (auto jversionid : jarchitecture.value().items()) | |
{ | |
// FIXME: do error checking for these | |
std::string checkrevision_filename = jversionid.value()["checkRevisionFile"].get<std::string>(); | |
std::string checkrevision_equation = jversionid.value()["equation"].get<std::string>(); | |
for (auto jentry : jversionid.value()["entries"]) | |
{ | |
try | |
{ | |
entry_total_count += 1; | |
VersionCheck entry(jentry["title"].get<std::string>(), | |
jversionid.key(), | |
jentry["version"].get<std::string>(), | |
jentry["hash"].get<std::string>(), | |
jarchitecture.key(), jclient.key(), | |
jentry["fileMetadata"].get<std::string>(), | |
jentry["versionTag"].get<std::string>()); | |
vc_entries.push_front(entry); | |
entry_success_count += 1; | |
} | |
catch (const std::exception& e) | |
{ | |
eventlog(eventlog_level_error, __FUNCTION__, "Failed to load versioncheck entry: {}", e.what()); | |
continue; | |
} | |
} | |
} | |
} | |
} | |
eventlog(eventlog_level_info, __FUNCTION__, "Successfully loaded {} out of {} versioncheck entries", entry_success_count, entry_total_count); | |
versioncheck_conf_is_loaded = true; | |
return true; | |
} | |
void unload_versioncheck_conf() | |
{ | |
if (versioncheck_conf_is_loaded) | |
{ | |
vc_entries.clear(); | |
eventlog(eventlog_level_info, __FUNCTION__, "Successfully unloaded all version check entries"); | |
versioncheck_conf_is_loaded = false; | |
} | |
else | |
{ | |
eventlog(eventlog_level_error, __FUNCTION__, "Caught attempt to unload all version check entries with no loaded entries"); | |
} | |
} | |
const checkrevision_file select_checkrevision_file(t_tag architecture, t_tag client, std::uint32_t version_id) | |
{ | |
static const checkrevision_file invalid_checkrevision = { "ver-IX86-1.mpq", "A=42 B=42 C=42 4 A=A^S B=B^B C=C^C A=A^S" }; | |
for (const auto& entry : vc_entries) | |
{ | |
if (entry.m_architecture == architecture | |
&& entry.m_client == client | |
&& entry.m_version_id == version_id) | |
{ | |
return entry; | |
} | |
} | |
return invalid_checkrevision; | |
} | |
VersionCheck::VersionCheck(const std::string& title, const std::string& version_id, const std::string& game_version, | |
const std::string& checksum, const std::string& architecture, const std::string& client, | |
const std::string& metadata, const std::string& version_tag) | |
{ | |
this->m_version_id = std::stoul(version_id.c_str(), nullptr, 0); // version byte | |
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); | |
this->m_architecture = tag_str_to_uint(architecture.c_str()); | |
if (!tag_check_arch(this->m_architecture)) | |
{ | |
throw std::runtime_error("Invalid architecture \"" + architecture + "\" in entry \"" + title + "\""); | |
} | |
this->m_client = tag_str_to_uint(client.c_str()); | |
if (!tag_check_client(this->m_client)) | |
{ | |
throw std::runtime_error("Invalid client \"" + client + "\" in entry \"" + title + "\""); | |
} | |
this->m_metadata = parse_file_metadata(metadata); | |
if (this->m_metadata.filename.empty() | |
&& this->m_metadata.file_size == 0 | |
&& this->m_metadata.timestamp == 0) | |
{ | |
throw std::runtime_error("Invalid file_metadata \"" + metadata + "\" in entry \"" + title + "\""); | |
} | |
// FIXME: check for uniqueness | |
this->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_info, __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_info, __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_info, __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_info, __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_info, __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_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 {}; | |
} | |
if (unparsed_metadata == "NULL") | |
{ | |
eventlog(eventlog_level_info, __FUNCTION__, "got \"NULL\" as 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(×tamp_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(×tamp_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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2000 Onlyer (onlyer@263.net) | |
* Copyright (C) 2001 Ross Combs (ross@bnetd.org) | |
* Copyright (C) 2002 Gianluigi Tiesi (sherpya@netfarm.it) | |
* Copyright (C) 2004 CreepLord (creeplord@pvpgn.org) | |
* | |
* This program is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU General Public License | |
* as published by the Free Software Foundation; either version 2 | |
* of the License, or (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program; if not, write to the Free Software | |
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
*/ | |
#ifndef PVPGN_BNETD_VERSIONCHECK_H | |
#define PVPGN_BNETD_VERSIONCHECK_H | |
#include <cstdint> | |
#include <ctime> | |
#include <string> | |
#include "common/tag.h" | |
namespace pvpgn | |
{ | |
namespace bnetd | |
{ | |
class VersionCheck; | |
struct file_metadata | |
{ | |
std::time_t timestamp; | |
std::uint64_t file_size; | |
std::string filename; | |
}; | |
struct checkrevision_file | |
{ | |
std::string filename; | |
std::string equation; | |
}; | |
bool load_versioncheck_conf(const std::string& filename); | |
void unload_versioncheck_conf(); | |
const checkrevision_file select_checkrevision_file(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; | |
/*******************************************************************************/ | |
// Getters | |
/*******************************************************************************/ | |
std::string get_version_tag() const noexcept; | |
/*******************************************************************************/ | |
// Deconstructors | |
/*******************************************************************************/ | |
~VersionCheck() = default; | |
private: | |
/*******************************************************************************/ | |
// Friend functions | |
/*******************************************************************************/ | |
friend bool load_versioncheck_conf(const std::string& filename); | |
friend const checkrevision_file select_checkrevision_file(t_tag architecture, t_tag client, std::uint32_t version_id); | |
/*******************************************************************************/ | |
// Constructors | |
/*******************************************************************************/ | |
VersionCheck(const std::string& title, const std::string& version_id, const std::string& game_version, | |
const std::string& checksum, const std::string& architecture, const std::string& client, | |
const std::string& metadata, 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; | |
struct file_metadata m_metadata; | |
std::string m_version_tag; | |
}; | |
} // namespace bnetd | |
} // namespace pvpgn | |
#endif //PVPGN_BNETD_VERSIONCHECK_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment