diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 8f7beb2bed3..46c8a24a9e0 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -24,15 +24,11 @@ jobs: - os: windows-2019 triplet: x64-windows packages: > - boost-asio boost-iostreams boost-system boost-filesystem - boost-variant boost-lockfree cryptopp curl jsoncpp - luajit libmariadb pugixml spdlog libzippp sccache + sccache - os: windows-2022 triplet: x64-windows packages: > - boost-asio boost-iostreams boost-system boost-filesystem - boost-variant boost-lockfree cryptopp curl jsoncpp - luajit libmariadb pugixml spdlog libzippp sccache + sccache steps: - name: Checkout repository diff --git a/cmake/FindCryptoPP.cmake b/cmake/FindCryptoPP.cmake deleted file mode 100644 index 9ac38face41..00000000000 --- a/cmake/FindCryptoPP.cmake +++ /dev/null @@ -1,108 +0,0 @@ -# Module for locating the Crypto++ encryption library. -# -# Customizable variables: -# CRYPTOPP_ROOT_DIR -# This variable points to the CryptoPP root directory. On Windows the -# library location typically will have to be provided explicitly using the -# -D command-line option. The directory should include the include/cryptopp, -# lib and/or bin sub-directories. -# -# Read-only variables: -# CRYPTOPP_FOUND -# Indicates whether the library has been found. -# -# CRYPTOPP_INCLUDE_DIRS -# Points to the CryptoPP include directory. -# -# CRYPTOPP_LIBRARIES -# Points to the CryptoPP libraries that should be passed to -# target_link_libararies. -# -# -# Copyright (c) 2012 Sergiu Dotenco -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -INCLUDE (FindPackageHandleStandardArgs) - -FIND_PATH (CRYPTOPP_ROOT_DIR - NAMES cryptopp/cryptlib.h include/cryptopp/cryptlib.h - PATHS ENV CRYPTOPPROOT - DOC "CryptoPP root directory") - -# Re-use the previous path: -FIND_PATH (CRYPTOPP_INCLUDE_DIR - NAMES cryptopp/cryptlib.h - HINTS ${CRYPTOPP_ROOT_DIR} - PATH_SUFFIXES include - DOC "CryptoPP include directory") - -FIND_LIBRARY (CRYPTOPP_LIBRARY_DEBUG - NAMES cryptlibd cryptoppd libcryptopp - HINTS ${CRYPTOPP_ROOT_DIR} - PATH_SUFFIXES lib - DOC "CryptoPP debug library") - -FIND_LIBRARY (CRYPTOPP_LIBRARY_RELEASE - NAMES cryptlib cryptopp libcryptopp - HINTS ${CRYPTOPP_ROOT_DIR} - PATH_SUFFIXES lib - DOC "CryptoPP release library") - -IF (CRYPTOPP_LIBRARY_DEBUG AND CRYPTOPP_LIBRARY_RELEASE) - SET (CRYPTOPP_LIBRARY - optimized ${CRYPTOPP_LIBRARY_RELEASE} - debug ${CRYPTOPP_LIBRARY_DEBUG} CACHE DOC "CryptoPP library") -ELSEIF (CRYPTOPP_LIBRARY_RELEASE) - SET (CRYPTOPP_LIBRARY ${CRYPTOPP_LIBRARY_RELEASE} CACHE DOC - "CryptoPP library") -ENDIF (CRYPTOPP_LIBRARY_DEBUG AND CRYPTOPP_LIBRARY_RELEASE) - -IF (CRYPTOPP_INCLUDE_DIR) - SET (_CRYPTOPP_VERSION_HEADER ${CRYPTOPP_INCLUDE_DIR}/cryptopp/config.h) - - IF (EXISTS ${_CRYPTOPP_VERSION_HEADER}) - FILE (STRINGS ${_CRYPTOPP_VERSION_HEADER} _CRYPTOPP_VERSION_TMP REGEX - "^#define CRYPTOPP_VERSION[ \t]+[0-9]+$") - - STRING (REGEX REPLACE - "^#define CRYPTOPP_VERSION[ \t]+([0-9]+)" "\\1" _CRYPTOPP_VERSION_TMP - ${_CRYPTOPP_VERSION_TMP}) - - STRING (REGEX REPLACE "([0-9]+)[0-9][0-9]" "\\1" CRYPTOPP_VERSION_MAJOR - ${_CRYPTOPP_VERSION_TMP}) - STRING (REGEX REPLACE "[0-9]([0-9])[0-9]" "\\1" CRYPTOPP_VERSION_MINOR - ${_CRYPTOPP_VERSION_TMP}) - STRING (REGEX REPLACE "[0-9][0-9]([0-9])" "\\1" CRYPTOPP_VERSION_PATCH - ${_CRYPTOPP_VERSION_TMP}) - - SET (CRYPTOPP_VERSION_COUNT 3) - SET (CRYPTOPP_VERSION - ${CRYPTOPP_VERSION_MAJOR}.${CRYPTOPP_VERSION_MINOR}.${CRYPTOPP_VERSION_PATCH}) - ENDIF (EXISTS ${_CRYPTOPP_VERSION_HEADER}) -ENDIF (CRYPTOPP_INCLUDE_DIR) - -SET (CRYPTOPP_INCLUDE_DIRS ${CRYPTOPP_INCLUDE_DIR}) -SET (CRYPTOPP_LIBRARIES ${CRYPTOPP_LIBRARY}) - -MARK_AS_ADVANCED (CRYPTOPP_INCLUDE_DIR CRYPTOPP_LIBRARY CRYPTOPP_LIBRARY_DEBUG - CRYPTOPP_LIBRARY_RELEASE) - -FIND_PACKAGE_HANDLE_STANDARD_ARGS (CryptoPP REQUIRED_VARS CRYPTOPP_ROOT_DIR - CRYPTOPP_INCLUDE_DIR CRYPTOPP_LIBRARY VERSION_VAR CRYPTOPP_VERSION) diff --git a/cmake/FindGMP.cmake b/cmake/FindGMP.cmake index 5064fda1ed8..96f3f43827f 100644 --- a/cmake/FindGMP.cmake +++ b/cmake/FindGMP.cmake @@ -1,127 +1,67 @@ -# -# - Find GMP/MPIR libraries and headers -# This module defines the following variables: -# -# GMP_FOUND - true if GMP/MPIR was found -# GMP_INCLUDE_DIRS - include search path -# GMP_LIBRARIES - libraries to link with -# GMP_LIBRARY_DLL - library DLL to install. Only available on WIN32. -# GMP_LIBRARIES_DIR - the directory the library we link with is found in. +# - Try to find the GMP libraries +# This module defines: +# GMP_FOUND - system has GMP lib +# GMP_INCLUDE_DIR - the GMP include directory +# GMP_LIBRARIES_DIR - directory where the GMP libraries are located +# GMP_LIBRARIES - Link these to use GMP +# TODO: support MacOSX -if (ANDROID) - - set( GMP_ROOT ${CMAKE_SOURCE_DIR}/../gmp/${ANDROID_ABI} ) - if (EXISTS ${GMP_ROOT} ) - message("Looking good for ${GMP_ROOT}") - set(GMP_INCLUDE_DIRS ${GMP_ROOT} CACHE PATH "include search path") - set(GMP_LIBRARIES ${GMP_ROOT}/libgmp.so CACHE FILEPATH "include search path") - set(GMP_LIBRARIES_DIR ${GMP_ROOT} CACHE PATH "include search path") - else() - message("Bad call: ${GMP_ROOT} does not exist") - endif() -set( GMP_ROOT ${CMAKE_SOURCE_DIR}/../../gmp/${ANDROID_ABI} ) -if (EXISTS ${GMP_ROOT} ) - message("Looking good for ${GMP_ROOT}") - set(GMP_INCLUDE_DIRS ${GMP_ROOT} CACHE PATH "include search path") - set(GMP_LIBRARIES ${GMP_ROOT}/libgmp.so CACHE FILEPATH "include search path") - set(GMP_LIBRARIES_DIR ${GMP_ROOT} CACHE PATH "include search path") - else() - message("Bad call: ${GMP_ROOT} does not exist") - endif() -find_path(GMP_INCLUDE_DIRS - NAMES gmp.h - HINTS ${GMP_ROOT} - NO_SYSTEM_ENVIRONMENT_PATH) - find_library(GMP_LIBRARIES NAMES gmp - PATHS - ${GMP_ROOT} - NO_SYSTEM_ENVIRONMENT_PATH) - - -elseif(MSVC) - find_library(GMP_LIBRARIES NAMES mpir mpird - PATHS - $ENV{GMP_ROOT} - $ENV{GMP_ROOT}/lib - ${GMP_ROOT} - ${GMP_ROOT}/lib - ${CMAKE_SOURCE_DIR}/../tools/mpir/lib - ${CMAKE_SOURCE_DIR}/../tools/mpird/lib - ${CMAKE_SOURCE_DIR}/../mpir/lib - ${CMAKE_SOURCE_DIR}/../mpird/lib - - $ENV{PROGRAMFILES}/mpir/lib - $ENV{PROGRAMFILES}/mpird/lib - $ENV{HOME}/mpir/lib - $ENV{HOME}/mpird/lib - ${CMAKE_INSTALL_PREFIX}/lib - DOC "Try first the MPIR DLL when in an Windows environment" - ) - - get_filename_component(GMP_LIBRARIES_DIR "${GMP_LIBRARIES}" PATH) - -find_file(GMP_LIBRARY_DLL NAMES mpir.dll mpird.dll - PATHS - ${GMP_LIBRARIES_DIR}/../bin - ${GMP_LIBRARIES_DIR} -) - - find_path(GMP_INCLUDE_DIRS - NAMES mpir.h mpird.h - PATHS - ${GMP_LIBRARIES_DIR}/../include - ${GMP_LIBRARIES_DIR} -) +include(FindPackageHandleStandardArgs) +if(GMP_INCLUDE_DIR) + set(GMP_in_cache TRUE) else() + set(GMP_in_cache FALSE) +endif() +if(NOT GMP_LIBRARIES) + set(GMP_in_cache FALSE) +endif() -#use GMP, notice that there are two cases, everything is the same directory, or everything is in -#its proper places - - - - find_library(GMP_LIBRARIES - NAMES gmp libgmp - HINTS - . - $ENV{GMP_ROOT} - $ENV{GMP_ROOT}/lib - ${GMP_ROOT} - ${GMP_ROOT}/lib - /usr/local/opt/gmp/lib - /opt/lib - /usr/local/lib - $ENV{HOME}/lib - ${CMAKE_INSTALL_PREFIX}/lib - ) - +# Is it already configured? +if( NOT GMP_in_cache ) + + find_path(GMP_INCLUDE_DIR + NAMES gmp.h + HINTS ENV GMP_INC_DIR + ENV GMP_DIR + ${CGAL_INSTALLATION_PACKAGE_DIR}/auxiliary/gmp/include + PATH_SUFFIXES include + DOC "The directory containing the GMP header files" + ) + + find_library(GMP_LIBRARY_RELEASE NAMES gmp libgmp-10 mpir + HINTS ENV GMP_LIB_DIR + ENV GMP_DIR + ${CGAL_INSTALLATION_PACKAGE_DIR}/auxiliary/gmp/lib + PATH_SUFFIXES lib + DOC "Path to the Release GMP library" + ) - find_path(GMP_INCLUDE_DIRS - NAMES gmp.h - HINTS - . - $ENV{GMP_ROOT} - $ENV{GMP_ROOT}/include - ${GMP_ROOT} - ${GMP_ROOT}/include - ${GMP_LIBRARIES_DIR}/../include - ${GMP_LIBRARIES_DIR} - ) -endif() + find_library(GMP_LIBRARY_DEBUG NAMES gmpd gmp libgmp-10 mpir + HINTS ENV GMP_LIB_DIR + ENV GMP_DIR + ${CGAL_INSTALLATION_PACKAGE_DIR}/auxiliary/gmp/lib + PATH_SUFFIXES lib + DOC "Path to the Debug GMP library" + ) -get_filename_component(GMP_LIBRARIES_DIR "${GMP_LIBRARIES}" PATH CACHE) + get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(IS_MULTI_CONFIG) + set(GMP_LIBRARIES debug ${GMP_LIBRARY_DEBUG} optimized ${GMP_LIBRARY_RELEASE}) + else() + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(GMP_LIBRARIES ${GMP_LIBRARY_DEBUG}) + else() + set(GMP_LIBRARIES ${GMP_LIBRARY_RELEASE}) + endif() + endif() + # Attempt to load a user-defined configuration for GMP if couldn't be found + if ( NOT GMP_INCLUDE_DIR OR NOT GMP_LIBRARIES) + include( GMPConfig OPTIONAL ) + endif() -# handle the QUIET and REQUIRED arguments and set GMP_FOUND to TRUE if -# all listed variables are true -include(FindPackageHandleStandardArgs) -if(MSVC) - find_package_handle_standard_args(GMP DEFAULT_MSG GMP_LIBRARIES GMP_LIBRARIES_DIR GMP_LIBRARY_DLL GMP_INCLUDE_DIRS) - mark_as_advanced(GMP_LIBRARY_DLL) -else() - find_package_handle_standard_args(GMP DEFAULT_MSG GMP_LIBRARIES GMP_LIBRARIES_DIR GMP_INCLUDE_DIRS) endif() - -mark_as_advanced(GMP_LIBRARIES GMP_LIBRARIES_DIR GMP_INCLUDE_DIRS) +find_package_handle_standard_args(GMP "DEFAULT_MSG" GMP_LIBRARIES GMP_INCLUDE_DIR) diff --git a/config.lua.dist b/config.lua.dist index 581cee0cc0d..0ed3c31b3b2 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -42,6 +42,11 @@ maxPacketsPerSecond = 25 maxItem = 2000 maxContainer = 100 +-- Packet Compression +-- Minimize network bandwith and reduce ping +-- Levels: 0 = disabled, 1 = best speed, 9 = best compression +packetCompressionLevel = 6 + -- Depot Limit freeDepotLimit = 2000 premiumDepotLimit = 10000 diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 5ef287b5c08..bfb81278498 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -2,8 +2,8 @@ FROM ubuntu:22.04 AS dependencies RUN apt-get update && apt-get install -y --no-install-recommends cmake git \ - libluajit-5.1-dev unzip build-essential ca-certificates curl zip unzip tar \ - pkg-config ninja-build \ + libluajit-5.1-dev unzip build-essential ca-certificates curl zip unzip tar \ + pkg-config ninja-build autoconf automake libtool \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 61a20d69d10..6d764401666 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,7 @@ set(GNUCXX_MINIMUM_VERSION 8) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(Boost_NO_WARN_NEW_VERSIONS ON) # Global gives error when building spdlog # Avoid changes by cmake/make on the source tree @@ -117,12 +118,11 @@ find_package(spdlog REQUIRED) find_package(LuaJIT REQUIRED) find_package(Threads REQUIRED) find_package(libzippp REQUIRED) -find_package(cryptopp CONFIG REQUIRED) find_package(Protobuf REQUIRED) +find_package(GMP REQUIRED) if (MSVC) find_package(Boost 1.53.0 COMPONENTS system filesystem iostreams date_time REQUIRED) - set(CRYPTOPP_LIBRARIES "cryptopp-static") find_package(CURL REQUIRED) find_package(jsoncpp REQUIRED) find_package(MySQL REQUIRED) @@ -271,7 +271,6 @@ target_sources(${PROJECT_NAME} otpch.cpp otserv.cpp security/rsa.cpp - security/xtea.cpp server/network/connection/connection.cpp server/network/message/networkmessage.cpp server/network/message/outputmessage.cpp @@ -309,10 +308,10 @@ if (MSVC) ${LUA_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ${PUGIXML_INCLUDE_DIR} - ${CRYPTOPP_INCLUDE_DIR} ${CURL_INCLUDE_DIRS} ${Protobuf_INCLUDE_DIRS} ${SPDLOG_INCLUDE_DIR} + ${GMP_INCLUDE_DIR} $ ) @@ -322,11 +321,11 @@ if (MSVC) ${LUA_LIBRARIES} ${Boost_LIBRARIES} ${PUGIXML_LIBRARIES} - ${CRYPTOPP_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${CURL_LIBRARIES} ${Protobuf_LIBRARIES} ${SPDLOG_LIBRARY} + ${GMP_LIBRARIES} fmt::fmt jsoncpp_lib libzippp::libzippp @@ -339,24 +338,25 @@ else() ${CMAKE_SOURCE_DIR}/src ${LUA_INCLUDE_DIR} ${Protobuf_INCLUDE_DIRS} + ${GMP_INCLUDE_DIRS} ) target_link_libraries(${PROJECT_NAME} PRIVATE + ${LUA_LIBRARIES} + ${Protobuf_LIBRARIES} + ${GMP_LIBRARIES} Boost::boost Boost::date_time Boost::filesystem Boost::iostreams Boost::system - cryptopp-static CURL::libcurl jsoncpp_static unofficial::libmariadb unofficial::mariadbclient pugixml::pugixml spdlog::spdlog Threads::Threads - ${LUA_LIBRARIES} - ${Protobuf_LIBRARIES} libzippp::libzippp ) diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp index 943a9689a21..cb34baa2538 100644 --- a/src/config/config_definitions.hpp +++ b/src/config/config_definitions.hpp @@ -141,6 +141,7 @@ enum integerConfig_t { MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER, EXP_FROM_PLAYERS_LEVEL_RANGE, MAX_PACKETS_PER_SECOND, + COMPRESSION_LEVEL, STORE_COIN_PACKET, DAY_KILLS_TO_RED, WEEK_KILLS_TO_RED, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index d4e937237df..e86f00e8a45 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -225,6 +225,7 @@ bool ConfigManager::load() integer[CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES] = getGlobalNumber(L, "checkExpiredMarketOffersEachMinutes", 60); integer[MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER] = getGlobalNumber(L, "maxMarketOffersAtATimePerPlayer", 100); integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25); + integer[COMPRESSION_LEVEL] = getGlobalNumber(L, "packetCompressionLevel", 6); integer[STORE_COIN_PACKET] = getGlobalNumber(L, "coinPacketSize", 25); integer[DAY_KILLS_TO_RED] = getGlobalNumber(L, "dayKillsToRedSkull", 3); integer[WEEK_KILLS_TO_RED] = getGlobalNumber(L, "weekKillsToRedSkull", 5); diff --git a/src/creatures/players/player.h b/src/creatures/players/player.h index 65bd9737677..f67a7b6c628 100644 --- a/src/creatures/players/player.h +++ b/src/creatures/players/player.h @@ -1152,7 +1152,7 @@ class Player final : public Creature, public Cylinder } } void sendDistanceShoot(const Position& from, const Position& to, - unsigned char type) const { + uint8_t type) const { if (client) { client->sendDistanceShoot(from, to, type); } diff --git a/src/otpch.h b/src/otpch.h index 4f961aca621..3346d2a879f 100644 --- a/src/otpch.h +++ b/src/otpch.h @@ -22,6 +22,7 @@ // Definitions should be global. #include "utils/definitions.h" +#include "utils/simd.hpp" #include #include diff --git a/src/otserv.cpp b/src/otserv.cpp index 76f27595274..bdff5d99bad 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -100,12 +100,18 @@ void loadModules() { SPDLOG_INFO("Server protocol: {}.{}", CLIENT_VERSION_UPPER, CLIENT_VERSION_LOWER); - // set RSA key + const char* p("14299623962416399520070177382898895550795403345466153217470516082934737582776038882967213386204600674145392845853859217990626450972452084065728686565928113"); + const char* q("7630979195970404721891201847792002125535401292779123937207447574596692788513647179235335529307251350570728407373705564708871762033017096809910315212884101"); try { - g_RSA().loadPEM("key.pem"); - } catch(const std::exception& e) { - SPDLOG_ERROR(e.what()); - startupErrorMessage(); + if (!g_RSA().loadPEM("key.pem")) { + // file doesn't exist - switch to base10-hardcoded keys + SPDLOG_ERROR("File key.pem not found or have problem on loading... Setting standard rsa key\n"); + g_RSA().setKey(p, q); + } + } catch (std::system_error const& e) { + SPDLOG_ERROR("Loading RSA Key from key.pem failed with error: {}\n", e.what()); + SPDLOG_ERROR("Switching to a default key..."); + g_RSA().setKey(p, q); } // Database diff --git a/src/security/rsa.cpp b/src/security/rsa.cpp index 4664de4cde5..5192d1cc6ff 100644 --- a/src/security/rsa.cpp +++ b/src/security/rsa.cpp @@ -1,6 +1,7 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Canary - A free and open-source MMORPG server emulator + * Copyright (C) 2021 OpenTibiaBR + * Copyright (C) 2019-2021 Saiyans King * * 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 @@ -21,63 +22,285 @@ #include "security/rsa.h" -#include -#include - #include -#include -static CryptoPP::AutoSeededRandomPool prng; +RSA::RSA() +{ + mpz_init(n); + mpz_init2(d, 1024); +} + +RSA::~RSA() = default; -void RSA2::decrypt(uint8_t* msg) const +void RSA::setKey(const char* pString, const char* qString, int base/* = 10*/) { - try - { - CryptoPP::Integer m{msg, 128}; - auto c = pk.CalculateInverse(prng, m); - c.Encode(msg, 128); + mpz_t p; + mpz_t q; + mpz_t e; + mpz_init2(p, 1024); + mpz_init2(q, 1024); + mpz_init(e); + + mpz_set_str(p, pString, base); + mpz_set_str(q, qString, base); + + // e = 65537 + mpz_set_ui(e, 65537); + + // n = p * q + mpz_mul(n, p, q); + + // d = e^-1 mod (p - 1)(q - 1) + mpz_t p_1; + mpz_t q_1; + mpz_t pq_1; + mpz_init2(p_1, 1024); + mpz_init2(q_1, 1024); + mpz_init2(pq_1, 1024); + + mpz_sub_ui(p_1, p, 1); + mpz_sub_ui(q_1, q, 1); + + // pq_1 = (p -1)(q - 1) + mpz_mul(pq_1, p_1, q_1); + + // d = e^-1 mod (p - 1)(q - 1) + mpz_invert(d, e, pq_1); + + mpz_clear(p_1); + mpz_clear(q_1); + mpz_clear(pq_1); + + mpz_clear(p); + mpz_clear(q); + mpz_clear(e); +} + +void RSA::decrypt(char* msg) const +{ + mpz_t c; + mpz_t m; + mpz_init2(c, 1024); + mpz_init2(m, 1024); + + mpz_import(c, 128, 1, 1, 0, 0, msg); + + // m = c^d mod n + mpz_powm(m, c, d, n); + + size_t count = (mpz_sizeinbase(m, 2) + 7) / 8; + memset(msg, 0, 128 - count); + mpz_export(msg + (128 - count), nullptr, 1, 1, 0, 0, m); + + mpz_clear(c); + mpz_clear(m); +} + +std::string RSA::base64Decrypt(const std::string& input) const +{ + auto posOfCharacter = [](const uint8_t chr) -> uint16_t { + if (chr >= 'A' && chr <= 'Z') { + return chr - 'A'; + } else if (chr >= 'a' && chr <= 'z') { + return chr - 'a' + ('Z' - 'A') + 1; + } else if (chr >= '0' && chr <= '9') { + return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + } else if (chr == '+' || chr == '-') { + return 62; + } else if (chr == '/' || chr == '_') { + return 63; + } + SPDLOG_ERROR("[RSA::base64Decrypt] - Invalid base6409"); + return 0; + }; + + if (input.empty()) { + return std::string(); } - catch (const CryptoPP::Exception &e) - { - SPDLOG_ERROR("[RSA2::decrypt - Exception] - {}", e.GetWhat()); - return; + + size_t length = input.length(); + size_t pos = 0; + + std::string output; + output.reserve(length / 4 * 3); + while (pos < length) { + uint16_t pos1 = posOfCharacter(input[pos + 1]); + output.push_back(static_cast(((posOfCharacter(input[pos])) << 2) + ((pos1 & 0x30) >> 4))); + if (input[pos + 2] != '=' && input[pos + 2] != '.') { + uint16_t pos2 = posOfCharacter(input[pos + 2]); + output.push_back(static_cast(((pos1 & 0x0f) << 4) + ((pos2 & 0x3c) >> 2))); + if (input[pos + 3] != '=' && input[pos + 3] != '.') { + output.push_back(static_cast(((pos2 & 0x03) << 6) + posOfCharacter(input[pos + 3]))); + } + } + + pos += 4; } + + return output; } -static const std::string header = "-----BEGIN RSA PRIVATE KEY-----"; -static const std::string footer = "-----END RSA PRIVATE KEY-----"; +static const std::string header_old = "-----BEGIN RSA PRIVATE KEY-----"; +static const std::string footer_old = "-----END RSA PRIVATE KEY-----"; +static const std::string header_new = "-----BEGIN PRIVATE KEY-----"; +static const std::string footer_new = "-----END PRIVATE KEY-----"; -void RSA2::loadPEM(const std::string& filename) +enum { - std::ifstream file{filename}; + CRYPT_RSA_ASN1_SEQUENCE = 48, + CRYPT_RSA_ASN1_INTEGER = 2, + CRYPT_RSA_ASN1_OBJECT = 6, + CRYPT_RSA_ASN1_BITSTRING = 3 +}; - std::ostringstream oss; - for (std::string line; std::getline(file, line); oss << line); - std::string key = oss.str(); +uint16_t RSA::decodeLength(char*& pos) const +{ + std::string buffer; + auto length = static_cast(static_cast(*pos++)); + if (length & 0x80) { + length &= 0x7F; + if (length > 4) { + SPDLOG_ERROR("[RSA::loadPEM] - Invalid 'length'"); + return 0; + } + buffer[0] = buffer[1] = buffer[2] = buffer[3] = 0; + switch (length) { + case 4: + buffer[3] = static_cast(*pos++); + case 3: + buffer[2] = static_cast(*pos++); + case 2: + buffer[1] = static_cast(*pos++); + case 1: + buffer[0] = static_cast(*pos++); + default: + break; + } + std::memcpy(&length, buffer.data(), sizeof(length)); + } + return length; +} - if (key.substr(0, header.size()) != header) { - throw std::runtime_error("Missing RSA private key header.\n>> There is no `key.pem` in the folder."); +void RSA::readHexString(char*& pos, uint16_t length, std::string& output) const +{ + output.reserve(static_cast(length) * 2); + for (uint16_t i = 0; i < length; ++i) + { + auto hex = static_cast(*pos++); + output.push_back("0123456789ABCDEF"[(hex >> 4) & 15]); + output.push_back("0123456789ABCDEF"[hex & 15]); } +} - if (key.substr(key.size() - footer.size(), footer.size()) != footer) { - throw std::runtime_error("Missing RSA private key footer."); +bool RSA::loadPEM(const std::string& filename) +{ + std::ifstream file{ filename }; + if (!file.is_open()) { + return false; } - key = key.substr(header.size(), key.size() - footer.size()); + std::string key; + std::string pString; + std::string qString; + for (std::string line; std::getline(file, line); key.append(line)); + + if (key.compare(0, header_old.size(), header_old) == 0) { + if (key.compare(key.size() - footer_old.size(), footer_old.size(), footer_old) != 0) { + SPDLOG_ERROR("[RSA::loadPEM] - Missing RSA private key footer"); + return false; + } + + key = base64Decrypt(key.substr(header_old.size(), key.size() - header_old.size() - footer_old.size())); + } else if (key.compare(0, header_new.size(), header_new) == 0) { + if (key.compare(key.size() - footer_new.size(), footer_new.size(), footer_new) != 0) { + SPDLOG_ERROR("[RSA::loadPEM] - Missing RSA private key footer"); + return false; + } - CryptoPP::ByteQueue queue; - CryptoPP::Base64Decoder decoder; - decoder.Attach(new CryptoPP::Redirector(queue)); - decoder.Put(reinterpret_cast(key.c_str()), key.size()); - decoder.MessageEnd(); + key = base64Decrypt(key.substr(header_new.size(), key.size() - header_new.size() - footer_new.size())); + } else { + SPDLOG_ERROR("[RSA::loadPEM] - Missing RSA private key header"); + return false; + } - try { - pk.BERDecodePrivateKey(queue, false, queue.MaxRetrievable()); + char* pos = &key[0]; + if (static_cast(*pos++) != CRYPT_RSA_ASN1_SEQUENCE) { + SPDLOG_ERROR("[RSA::loadPEM] - Invalid unsupported RSA key"); + return false; + } - if (!pk.Validate(prng, 3)) { - throw std::runtime_error("RSA private key is not valid."); + uint16_t length = decodeLength(pos); + if (length != key.length() - std::distance(&key[0], pos)) { + SPDLOG_ERROR("[RSA::loadPEM] - Invalid unsupported RSA key"); + return false; + } + + auto tag = static_cast(*pos++); + if (tag == CRYPT_RSA_ASN1_INTEGER && static_cast(*(pos + 0)) == 0x01 && static_cast(*(pos + 1)) == 0x00 && static_cast(*(pos + 2)) == 0x30) { + pos += 3; + tag = CRYPT_RSA_ASN1_SEQUENCE; + } + + if (tag == CRYPT_RSA_ASN1_SEQUENCE) { + pos += decodeLength(pos); + tag = static_cast(*pos++); + decodeLength(pos); + if (tag == CRYPT_RSA_ASN1_BITSTRING) { + ++pos; + } + if (static_cast(*pos++) != CRYPT_RSA_ASN1_SEQUENCE) { + SPDLOG_ERROR("[RSA::loadPEM] - Invalid unsupported RSA key"); + return false; } - } catch (const CryptoPP::Exception& e) { - SPDLOG_ERROR("{}", e.what()); + + length = decodeLength(pos); + if (length != key.length() - std::distance(&key[0], pos)) { + SPDLOG_ERROR("[RSA::loadPEM] - Invalid unsupported RSA key"); + return false; + } + + tag = static_cast(*pos++); } + + if (tag != CRYPT_RSA_ASN1_INTEGER) { + SPDLOG_ERROR("[RSA::loadPEM] - Invalid unsupported RSA key"); + return false; + } + + length = decodeLength(pos); + pos += length; + if (length != 1 || static_cast(*pos) > 2) { + //public key - we don't have any interest in it + SPDLOG_ERROR("[RSA::loadPEM] - Invalid unsupported RSA key"); + return false; + } + + tag = static_cast(*pos++); + if (tag != CRYPT_RSA_ASN1_INTEGER) { + SPDLOG_ERROR("[RSA::loadPEM] - Invalid unsupported RSA key"); + return false; + } + + length = decodeLength(pos); + pos += length + 1; // Modulus - we don't care + length = decodeLength(pos); + pos += length + 1; // Public Exponent - we don't care + length = decodeLength(pos); + pos += length + 1; // Private Exponent - we don't care + length = decodeLength(pos); + readHexString(pos, length, pString); + ++pos; + length = decodeLength(pos); + readHexString(pos, length, qString); + ++pos; + length = decodeLength(pos); + pos += length + 1; // Prime Exponent P - we don't care + length = decodeLength(pos); + pos += length + 1; // Prime Exponent Q - we don't care + length = decodeLength(pos); + pos += length + 1; // Coefficient - we don't care + ++pos; + + setKey(pString.c_str(), qString.c_str(), 16); + return true; } diff --git a/src/security/rsa.h b/src/security/rsa.h index c5e351ee824..6d09b838664 100644 --- a/src/security/rsa.h +++ b/src/security/rsa.h @@ -1,6 +1,7 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Canary - A free and open-source MMORPG server emulator + * Copyright (C) 2021 OpenTibiaBR + * Copyright (C) 2019-2021 Saiyans King * * 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 @@ -20,33 +21,40 @@ #ifndef SRC_SECURITY_RSA_H_ #define SRC_SECURITY_RSA_H_ -#include +#include #include -class RSA2 +class RSA { public: - RSA2() = default; + RSA(); + ~RSA(); - // non-copyable - RSA2(const RSA2&) = delete; - RSA2& operator=(const RSA2&) = delete; + // Singleton - ensures we don't accidentally copy it + RSA(RSA const&) = delete; + void operator=(RSA const&) = delete; - static RSA2& getInstance() { + static RSA& getInstance() { // Guaranteed to be destroyed - static RSA2 instance; + static RSA instance; // Instantiated on first use return instance; } - void loadPEM(const std::string& filename); - void decrypt(uint8_t* msg) const; + void setKey(const char* pString, const char* qString, int base = 10); + void decrypt(char* msg) const; + + std::string base64Decrypt(const std::string& input) const; + uint16_t decodeLength(char*& pos) const; + void readHexString(char*& pos, uint16_t length, std::string& output) const; + bool loadPEM(const std::string& filename); private: - CryptoPP::RSA::PrivateKey pk; + mpz_t n; + mpz_t d; }; -constexpr auto g_RSA = &RSA2::getInstance; +constexpr auto g_RSA = &RSA::getInstance; #endif // SRC_SECURITY_RSA_H_ diff --git a/src/security/xtea.cpp b/src/security/xtea.cpp deleted file mode 100644 index 2478b7997e6..00000000000 --- a/src/security/xtea.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2020 Mark Samman - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" - -#include "security/xtea.h" - -#include -#include - -namespace xtea { - -namespace { - -constexpr uint32_t delta = 0x9E3779B9; - -template -void apply_rounds(uint8_t* data, size_t length, Round round) { - for (auto j = 0u; j < length; j += 8) { - uint32_t left = data[j+0] | data[j+1] << 8u | data[j+2] << 16u | data[j+3] - << 24u, - right = data[j+4] | data[j+5] << 8u | data[j+6] << 16u | data[j+7] - << 24u; - - round(left, right); - - data[j] = static_cast(left); - data[j+1] = static_cast(left >> 8u); - data[j+2] = static_cast(left >> 16u); - data[j+3] = static_cast(left >> 24u); - data[j+4] = static_cast(right); - data[j+5] = static_cast(right >> 8u); - data[j+6] = static_cast(right >> 16u); - data[j+7] = static_cast(right >> 24u); - } -} -} // namespace - -void encrypt(uint8_t* data, size_t length, const key& k) { - for (uint32_t i = 0, sum = 0, next_sum = sum + delta; i < 32; ++i, - sum = next_sum, next_sum += delta) { - apply_rounds(data, length, [&](uint32_t& left, uint32_t& right) { - left += ((right << 4 ^ right >> 5) + right) ^ (sum + k[sum & 3]); - right += ((left << 4 ^ left >> 5) + left) ^ - (next_sum + k[(next_sum >> 11) & 3]); - }); - } -} - -void decrypt(uint8_t* data, size_t length, const key& k) { - for (uint32_t i = 0, sum = delta << 5, next_sum = sum - delta; i < 32; ++i, - sum = next_sum, next_sum -= delta) { - apply_rounds(data, length, [&](uint32_t& left, uint32_t& right) { - right -= ((left << 4 ^ left >> 5) + left) ^ (sum + k[(sum >> 11) & 3]); - left -= ((right << 4 ^ right >> 5) + right) ^ - (next_sum + k[next_sum & 3]); - }); - } -} - -} // namespace xtea diff --git a/src/security/xtea.h b/src/security/xtea.h deleted file mode 100644 index 6b4c80a6c97..00000000000 --- a/src/security/xtea.h +++ /dev/null @@ -1,32 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef SRC_SECURITY_XTEA_H_ -#define SRC_SECURITY_XTEA_H_ - -namespace xtea { - -using key = std::array; - -void encrypt(uint8_t* data, size_t length, const key& k); -void decrypt(uint8_t* data, size_t length, const key& k); - -} // namespace xtea - -#endif // SRC_SECURITY_XTEA_H_ diff --git a/src/server/network/connection/connection.cpp b/src/server/network/connection/connection.cpp index 3924ea2e6a5..7456ccda859 100644 --- a/src/server/network/connection/connection.cpp +++ b/src/server/network/connection/connection.cpp @@ -26,7 +26,6 @@ #include "game/scheduling/scheduler.h" #include "server/server.h" - Connection_ptr ConnectionManager::createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort) { std::lock_guard lockClass(connectionManagerLock); @@ -59,6 +58,16 @@ void ConnectionManager::closeAll() } // Connection +// Constructor +Connection::Connection(boost::asio::io_service& initIoService, ConstServicePort_ptr initservicePort) : + readTimer(initIoService), + writeTimer(initIoService), + service_port(std::move(initservicePort)), + socket(initIoService) +{ + timeConnected = time(nullptr); +} +// Constructor end void Connection::close(bool force) { @@ -66,11 +75,14 @@ void Connection::close(bool force) ConnectionManager::getInstance().releaseConnection(shared_from_this()); std::lock_guard lockClass(connectionLock); - connectionState = CONNECTION_STATE_DISCONNECTED; + if (connectionState == CONNECTION_STATE_CLOSED) { + return; + } + connectionState = CONNECTION_STATE_CLOSED; if (protocol) { g_dispatcher().addTask( - createTask(std::bind(&Protocol::release, protocol))); + createSchedulerTask(1000, std::bind(&Protocol::release, protocol))); } if (messageQueue.empty() || force) { @@ -89,54 +101,47 @@ void Connection::closeSocket() boost::system::error_code error; socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); socket.close(error); - } catch (boost::system::system_error& e) { - SPDLOG_ERROR("[Connection::closeSocket] - {}", e.what()); + } catch (const boost::system::system_error& e) { + SPDLOG_ERROR("[Connection::closeSocket] - error: {}", e.what()); } } } -Connection::~Connection() +void Connection::accept(Protocol_ptr protocolPtr) { - closeSocket(); -} - -void Connection::accept(Protocol_ptr conProtocol) -{ - this->protocol = conProtocol; - g_dispatcher().addTask(createTask(std::bind(&Protocol::onConnect, protocol))); - connectionState = CONNECTION_STATE_CONNECTING_STAGE2; + this->connectionState = CONNECTION_STATE_IDENTIFYING; + this->protocol = protocolPtr; + g_dispatcher().addTask(createSchedulerTask(1000, std::bind(&Protocol::onConnect, protocolPtr))); - accept(); + // Call second accept for not duplicate code + accept(false); } -void Connection::accept() +void Connection::accept(bool toggleParseHeader /* = true */) { - if (connectionState == CONNECTION_STATE_PENDING) { - connectionState = CONNECTION_STATE_CONNECTING_STAGE1; - } - std::lock_guard lockClass(connectionLock); try { readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT)); readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); - - if (!receivedLastChar && receivedName && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) { + + // If toggleParseHeader is true, execute the parseHeader, if not, execute parseProxyIdentification + if (toggleParseHeader) { // Read size of the first packet boost::asio::async_read(socket, - boost::asio::buffer(msg.getBuffer(), 1), - std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + boost::asio::buffer(msg.getBuffer(), HEADER_LENGTH), + std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); } else { - // Read size of the first packet + // Read header bytes to identify if it is proxy identification boost::asio::async_read(socket, - boost::asio::buffer(msg.getBuffer(), HEADER_LENGTH), - std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + boost::asio::buffer(msg.getBuffer(), HEADER_LENGTH), + std::bind(&Connection::parseProxyIdentification, shared_from_this(), std::placeholders::_1)); } - } catch (boost::system::system_error& e) { - SPDLOG_ERROR("[Connection::accept] - {}", e.what()); + } catch (const boost::system::system_error& e) { + SPDLOG_ERROR("[Connection::accept] - error: {}", e.what()); close(FORCE_CLOSE); } } -void Connection::parseHeader(const boost::system::error_code& error) +void Connection::parseProxyIdentification(const boost::system::error_code& error) { std::lock_guard lockClass(connectionLock); readTimer.cancel(); @@ -144,60 +149,72 @@ void Connection::parseHeader(const boost::system::error_code& error) if (error) { close(FORCE_CLOSE); return; - } else if (connectionState == CONNECTION_STATE_DISCONNECTED) { + } else if (connectionState == CONNECTION_STATE_CLOSED) { return; } - uint32_t timePassed = std::max(1, (time(nullptr) - timeConnected) + 1); - if ((++packetsSent / timePassed) > static_cast(g_configManager().getNumber(MAX_PACKETS_PER_SECOND))) { - SPDLOG_INFO("{} disconnected for exceeding packet per second limit", convertIPToString(getIP())); - close(); + uint8_t* msgBuffer = msg.getBuffer(); + auto charData = static_cast(static_cast(msgBuffer)); + std::string serverName = g_configManager().getString(SERVER_NAME) + "\n"; + if (connectionState == CONNECTION_STATE_IDENTIFYING) { + if (msgBuffer[1] == 0x00 || strncasecmp(charData, &serverName[0], 2) != 0) { + //Probably not proxy identification so let's try standard parsing method + connectionState = CONNECTION_STATE_OPEN; + parseHeader(error); return; - } - - if (!receivedLastChar && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) { - uint8_t* msgBuffer = msg.getBuffer(); - - if (!receivedName && msgBuffer[1] == 0x00) { - receivedLastChar = true; } else { - std::string serverName = g_configManager().getString(SERVER_NAME) + "\n"; - - if (!receivedName) { - if (static_cast(msgBuffer[0]) == serverName[0] - && static_cast(msgBuffer[1]) == serverName[1]) { - receivedName = true; - serverNameTime = 1; - - accept(); - return; - } else { - SPDLOG_ERROR("Connection::parseHeader] " - "Invalid Client Login! Server Name mismatch!"); - close(FORCE_CLOSE); - return; + size_t remainder = serverName.length()-2; + if (remainder > 0) { + connectionState = CONNECTION_STATE_READINGS; + try { + readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); + + // Read the remainder of proxy identification + boost::asio::async_read(socket, + boost::asio::buffer(msg.getBuffer(), remainder), + std::bind(&Connection::parseProxyIdentification, shared_from_this(), std::placeholders::_1)); } - } - ++serverNameTime; - - if (static_cast(msgBuffer[0]) == serverName[serverNameTime]) { - if (msgBuffer[0] == 0x0A) { - receivedLastChar = true; + catch (const boost::system::system_error& e) { + SPDLOG_ERROR("Connection::parseProxyIdentification] - error: {}", e.what()); + close(FORCE_CLOSE); } - - accept(); return; } else { - SPDLOG_ERROR("Connection::parseHeader] " - "Invalid Client Login! Server Name mismatch!"); - close(FORCE_CLOSE); - return; + connectionState = CONNECTION_STATE_OPEN; } } + } else if (connectionState == CONNECTION_STATE_READINGS) { + size_t remainder = serverName.length() - 2; + if (strncasecmp(charData, &serverName[2], remainder) == 0) { + connectionState = CONNECTION_STATE_OPEN; + } else { + SPDLOG_ERROR("Connection::parseProxyIdentification] Invalid Client Login! Server Name mismatch!"); + close(FORCE_CLOSE); + return; + } + } + + accept(true); +} + +void Connection::parseHeader(const boost::system::error_code& error) +{ + std::lock_guard lockClass(connectionLock); + readTimer.cancel(); + + if (error) { + close(FORCE_CLOSE); + return; + } else if (connectionState == CONNECTION_STATE_CLOSED) { + return; } - if (receivedLastChar && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) { - connectionState = CONNECTION_STATE_GAME; + uint32_t timePassed = std::max(1, (time(nullptr) - timeConnected) + 1); + if ((++packetsSent / timePassed) > static_cast(g_configManager().getNumber(MAX_PACKETS_PER_SECOND))) { + SPDLOG_WARN("{} disconnected for exceeding packet per second limit.", convertIPToString(getIP())); + close(); + return; } if (timePassed > 2) { @@ -206,22 +223,22 @@ void Connection::parseHeader(const boost::system::error_code& error) } uint16_t size = msg.getLengthHeader(); - if (size == 0 || size >= NETWORKMESSAGE_MAXSIZE - 16) { + if (size == 0 || size > INPUTMESSAGE_MAXSIZE) { close(FORCE_CLOSE); return; } try { readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT)); - readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), - std::placeholders::_1)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); // Read packet content msg.setLength(size + HEADER_LENGTH); - boost::asio::async_read(socket, boost::asio::buffer(msg.getBodyBuffer(), size), - std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1)); - } catch (boost::system::system_error& e) { - SPDLOG_ERROR("[Connection::parseHeader] - {}", e.what()); + boost::asio::async_read(socket, + boost::asio::buffer(msg.getBodyBuffer(), size), + std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1)); + } catch (const boost::system::system_error& e) { + SPDLOG_ERROR("[Connection::parseHeader] - error: {}", e.what()); close(FORCE_CLOSE); } } @@ -234,84 +251,115 @@ void Connection::parsePacket(const boost::system::error_code& error) if (error) { close(FORCE_CLOSE); return; - } else if (connectionState == CONNECTION_STATE_DISCONNECTED) { + } else if (connectionState == CONNECTION_STATE_CLOSED) { return; } - //Check packet - uint32_t recvPacket = msg.get(); - if ((recvPacket & 1 << 31) != 0) { - //SPDLOG_INFO("CCompress"); - } - + bool skipReadingNextPacket = false; if (!receivedFirst) { // First message received receivedFirst = true; if (!protocol) { - // As of 11.11+ update, we need to check if it's a outdated client or a status client server with this ugly check - if (msg.getLength() < 280) { - msg.skipBytes(-CHECKSUM_LENGTH); //those 32bits read up there + //Check packet checksum + uint32_t checksum; + if (int32_t len = msg.getLength() - msg.getBufferPosition() - CHECKSUM_LENGTH; + len > 0) + { + checksum = adlerChecksum(msg.getBuffer() + msg.getBufferPosition() + CHECKSUM_LENGTH, len); + } else { + checksum = 0; + } + + uint32_t recvChecksum = msg.get(); + if (recvChecksum != checksum) { + // it might not have been the checksum, step back + msg.skipBytes(-CHECKSUM_LENGTH); } // Game protocol has already been created at this point - protocol = service_port->make_protocol(true, msg, shared_from_this()); + protocol = service_port->make_protocol(recvChecksum == checksum, msg, shared_from_this()); if (!protocol) { close(FORCE_CLOSE); return; } } else { - msg.skipBytes(1); // Skip protocol ID + // It is rather hard to detect if we have checksum or sequence method here so let's skip checksum check + // it doesn't generate any problem because olders protocol don't use 'server sends first' feature + msg.get(); + // Skip protocol ID + msg.skipBytes(1); } protocol->onRecvFirstMessage(msg); } else { - protocol->onRecvMessage(msg); // Send the packet to the current protocol + // Send the packet to the current protocol + skipReadingNextPacket = protocol->onRecvMessage(msg); } try { readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT)); - readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), - std::placeholders::_1)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); + + if (!skipReadingNextPacket) { + // Wait to the next packet + boost::asio::async_read(socket, boost::asio::buffer(msg.getBuffer(), HEADER_LENGTH), std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + } + } catch (const boost::system::system_error& e) { + SPDLOG_ERROR("[Connection::parsePacket] - error: {}", e.what()); + close(FORCE_CLOSE); + } +} + +void Connection::resumeWork() +{ + std::lock_guard lockClass(connectionLock); + try { // Wait to the next packet - boost::asio::async_read(socket, - boost::asio::buffer(msg.getBuffer(), HEADER_LENGTH), - std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); - } catch (boost::system::system_error& e) { - SPDLOG_ERROR("[Connection::parsePacket] - {}", e.what()); + boost::asio::async_read(socket, boost::asio::buffer(msg.getBuffer(), HEADER_LENGTH), std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + } catch (const boost::system::system_error& e) { + SPDLOG_ERROR("[Connection::resumeWork] - error: {}", e.what()); close(FORCE_CLOSE); } } -void Connection::send(const OutputMessage_ptr& conMsg) +void Connection::send(const OutputMessage_ptr& outputMessage) { std::lock_guard lockClass(connectionLock); - if (connectionState == CONNECTION_STATE_DISCONNECTED) { + if (connectionState == CONNECTION_STATE_CLOSED) { return; } bool noPendingWrite = messageQueue.empty(); - messageQueue.emplace_back(conMsg); + messageQueue.emplace_back(outputMessage); if (noPendingWrite) { - internalSend(conMsg); + // Make asio thread handle xtea encryption instead of dispatcher + try { + #if BOOST_VERSION >= 106600 + boost::asio::post(socket.get_executor(), std::bind(&Connection::internalWorker, shared_from_this())); + #else + socket.get_io_service().post(std::bind(&Connection::internalWorker, shared_from_this())); + #endif + } catch (const boost::system::system_error& e) { + SPDLOG_ERROR("[Connection::send] - error: {}", e.what()); + messageQueue.clear(); + close(FORCE_CLOSE); + } } } -void Connection::internalSend(const OutputMessage_ptr& conMsg) +void Connection::internalWorker() { - protocol->onSendMessage(conMsg); - try { - writeTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_WRITE_TIMEOUT)); - writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), - std::placeholders::_1)); - - boost::asio::async_write(socket, - boost::asio::buffer(conMsg->getOutputBuffer(), conMsg->getLength()), - std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); - } catch (boost::system::system_error& e) { - SPDLOG_ERROR("[Connection::internalSend] - {}", e.what()); - close(FORCE_CLOSE); + std::unique_lock lockClass(connectionLock); + if (!messageQueue.empty()) { + const OutputMessage_ptr& outputMessage = messageQueue.front(); + lockClass.unlock(); + protocol->onSendMessage(outputMessage); + lockClass.lock(); + internalSend(outputMessage); + } else if (connectionState == CONNECTION_STATE_CLOSED) { + closeSocket(); } } @@ -329,9 +377,23 @@ uint32_t Connection::getIP() return htonl(endpoint.address().to_v4().to_ulong()); } +void Connection::internalSend(const OutputMessage_ptr& outputMessage) +{ + try { + writeTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_WRITE_TIMEOUT)); + writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); + + boost::asio::async_write(socket, + boost::asio::buffer(outputMessage->getOutputBuffer(), outputMessage->getLength()), + std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); + } catch (const boost::system::system_error& e) { + SPDLOG_ERROR("[Connection::internalSend] - error: {}", e.what()); + } +} + void Connection::onWriteOperation(const boost::system::error_code& error) { - std::lock_guard lockClass(connectionLock); + std::unique_lock lockClass(connectionLock); writeTimer.cancel(); messageQueue.pop_front(); @@ -342,8 +404,12 @@ void Connection::onWriteOperation(const boost::system::error_code& error) } if (!messageQueue.empty()) { - internalSend(messageQueue.front()); - } else if (connectionState == CONNECTION_STATE_DISCONNECTED) { + const OutputMessage_ptr& outputMessage = messageQueue.front(); + lockClass.unlock(); + protocol->onSendMessage(outputMessage); + lockClass.lock(); + internalSend(outputMessage); + } else if (connectionState == CONNECTION_STATE_CLOSED) { closeSocket(); } } diff --git a/src/server/network/connection/connection.h b/src/server/network/connection/connection.h index fe38e4a51f8..7bb49150fd8 100644 --- a/src/server/network/connection/connection.h +++ b/src/server/network/connection/connection.h @@ -22,6 +22,7 @@ #ifndef SRC_SERVER_NETWORK_CONNECTION_CONNECTION_H_ #define SRC_SERVER_NETWORK_CONNECTION_CONNECTION_H_ +#include #include #include "declarations.hpp" @@ -55,7 +56,7 @@ class ConnectionManager void releaseConnection(const Connection_ptr& connection); void closeAll(); - protected: + private: ConnectionManager() = default; std::unordered_set connections; @@ -65,38 +66,30 @@ class ConnectionManager class Connection : public std::enable_shared_from_this { public: - // non-copyable - Connection(const Connection&) = delete; - Connection& operator=(const Connection&) = delete; + // Constructor + Connection(boost::asio::io_service& initIoService, + ConstServicePort_ptr initservicePort); + // Constructor end - Connection(boost::asio::io_service& init_io_service, - ConstServicePort_ptr init_service_port) : - readTimer(init_io_service), - writeTimer(init_io_service), - service_port(std::move(init_service_port)), - socket(init_io_service) { - connectionState = CONNECTION_STATE_PENDING; - packetsSent = 0; - timeConnected = time(nullptr); - receivedFirst = false; - serverNameTime = 0; - receivedName = false; - receivedLastChar = false; - } - ~Connection(); + // Destructor + ~Connection() = default; - friend class ConnectionManager; + // Singleton - ensures we don't accidentally copy it + Connection(const Connection&) = delete; + Connection& operator=(const Connection&) = delete; void close(bool force = false); // Used by protocols that require server to send first - void accept(Protocol_ptr protocol); - void accept(); + void accept(Protocol_ptr protocolPtr); + void accept(bool toggleParseHeader = true); - void send(const OutputMessage_ptr& msg); + void resumeWork(); + void send(const OutputMessage_ptr& outputMessage); uint32_t getIP(); private: + void parseProxyIdentification(const boost::system::error_code& error); void parseHeader(const boost::system::error_code& error); void parsePacket(const boost::system::error_code& error); @@ -105,12 +98,12 @@ class Connection : public std::enable_shared_from_this static void handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error); void closeSocket(); - void internalSend(const OutputMessage_ptr& msg); + void internalWorker(); + void internalSend(const OutputMessage_ptr& outputMessage); boost::asio::ip::tcp::socket& getSocket() { return socket; } - friend class ServicePort; NetworkMessage msg; @@ -127,14 +120,13 @@ class Connection : public std::enable_shared_from_this boost::asio::ip::tcp::socket socket; time_t timeConnected; - uint32_t packetsSent; + uint32_t packetsSent = 0; - int8_t connectionState; - bool receivedFirst; + std::underlying_type_t connectionState = CONNECTION_STATE_OPEN; + bool receivedFirst = false; - uint32_t serverNameTime; - bool receivedName; - bool receivedLastChar; + friend class ServicePort; + friend class ConnectionManager; }; #endif // SRC_SERVER_NETWORK_CONNECTION_CONNECTION_H_ diff --git a/src/server/network/message/networkmessage.cpp b/src/server/network/message/networkmessage.cpp index 7f185752a7e..ea5da2651a9 100644 --- a/src/server/network/message/networkmessage.cpp +++ b/src/server/network/message/networkmessage.cpp @@ -87,9 +87,11 @@ void NetworkMessage::addBytes(const char* bytes, size_t size) void NetworkMessage::addPaddingBytes(size_t n) { + #define canAdd(size) ((size + info.position) < NETWORKMESSAGE_MAXSIZE) if (!canAdd(n)) { return; } + #undef canAdd memset(buffer + info.position, 0x33, n); info.length += n; diff --git a/src/server/network/message/outputmessage.cpp b/src/server/network/message/outputmessage.cpp index d06a409c976..3ee64740aa8 100644 --- a/src/server/network/message/outputmessage.cpp +++ b/src/server/network/message/outputmessage.cpp @@ -30,8 +30,8 @@ const std::chrono::milliseconds OUTPUTMESSAGE_AUTOSEND_DELAY {10}; void OutputMessagePool::scheduleSendAll() { - auto functor = std::bind(&OutputMessagePool::sendAll, this); - g_scheduler().addEvent(createSchedulerTask(OUTPUTMESSAGE_AUTOSEND_DELAY.count(), functor)); + auto function = std::bind(&OutputMessagePool::sendAll, this); + g_scheduler().addEvent(createSchedulerTask(OUTPUTMESSAGE_AUTOSEND_DELAY.count(), function)); } void OutputMessagePool::sendAll() @@ -63,7 +63,7 @@ void OutputMessagePool::removeProtocolFromAutosend(const Protocol_ptr& protocol) //dispatcher thread auto it = std::find(bufferedProtocols.begin(), bufferedProtocols.end(), protocol); if (it != bufferedProtocols.end()) { - std::swap(*it, bufferedProtocols.back()); + *it = bufferedProtocols.back(); bufferedProtocols.pop_back(); } } diff --git a/src/server/network/message/outputmessage.h b/src/server/network/message/outputmessage.h index 96ea2041416..4ec3f77b40e 100644 --- a/src/server/network/message/outputmessage.h +++ b/src/server/network/message/outputmessage.h @@ -43,11 +43,9 @@ class OutputMessage : public NetworkMessage add_header(info.length); } - void addCryptoHeader(uint8_t addChecksum, uint32_t& sequence) { - if (addChecksum == 1) { - add_header(adlerChecksum(buffer + outputBufferStart, info.length)); - } else if (addChecksum == 2) { - add_header(sequence++); + void addCryptoHeader(bool addChecksum, uint32_t checksum) { + if (addChecksum) { + add_header(checksum); } writeMessageLength(); @@ -55,14 +53,14 @@ class OutputMessage : public NetworkMessage void append(const NetworkMessage& msg) { auto msgLen = msg.getLength(); - memcpy(buffer + info.position, msg.getBuffer() + 8, msgLen); + memcpy(buffer + info.position, msg.getBuffer() + INITIAL_BUFFER_POSITION, msgLen); info.length += msgLen; info.position += msgLen; } void append(const OutputMessage_ptr& msg) { auto msgLen = msg->getLength(); - memcpy(buffer + info.position, msg->getBuffer() + 8, msgLen); + memcpy(buffer + info.position, msg->getBuffer() + INITIAL_BUFFER_POSITION, msgLen); info.length += msgLen; info.position += msgLen; } diff --git a/src/server/network/protocol/protocol.cpp b/src/server/network/protocol/protocol.cpp index 59112f18b5b..d725f856073 100644 --- a/src/server/network/protocol/protocol.cpp +++ b/src/server/network/protocol/protocol.cpp @@ -22,32 +22,97 @@ #include "server/network/protocol/protocol.h" #include "server/network/message/outputmessage.h" #include "security/rsa.h" -#include "security/xtea.h" +#include "game/scheduling/tasks.h" +Protocol::~Protocol() = default; void Protocol::onSendMessage(const OutputMessage_ptr& msg) { if (!rawMessages) { + uint32_t sendMessageChecksum = 0; + if (compreesionEnabled && msg->getLength() >= 128 && compression(*msg)) { + sendMessageChecksum = (1U << 31); + } + msg->writeMessageLength(); - if (encryptionEnabled) { - XTEA_encrypt(*msg); - if (!compactCrypt) { - msg->addCryptoHeader((checksumEnabled ? 1 : 0), sequenceNumber); - } else { - msg->addCryptoHeader(2, sequenceNumber); + if (!encryptionEnabled) { + return; + } + + XTEA_encrypt(*msg); + if (checksumMethod == CHECKSUM_METHOD_NONE) { + msg->addCryptoHeader(false, 0); + } else if (checksumMethod == CHECKSUM_METHOD_ADLER32) { + msg->addCryptoHeader(true, adlerChecksum(msg->getOutputBuffer(), msg->getLength())); + } else if (checksumMethod == CHECKSUM_METHOD_SEQUENCE) { + msg->addCryptoHeader(true, sendMessageChecksum | (++serverSequenceNumber)); + if (serverSequenceNumber >= 0x7FFFFFFF) { + serverSequenceNumber = 0; } } } } -void Protocol::onRecvMessage(NetworkMessage& msg) +bool Protocol::sendRecvMessageCallback(NetworkMessage& msg) { if (encryptionEnabled && !XTEA_decrypt(msg)) { - return; + SPDLOG_ERROR("[Protocol::onRecvMessage] - XTEA_decrypt Failed"); + return false; } - parsePacket(msg); + auto protocolWeak = std::weak_ptr(shared_from_this()); + std::function callback = [protocolWeak, &msg]() { + if (auto protocol = protocolWeak.lock()) { + if (auto protocolConnection = protocol->getConnection()) { + protocol->parsePacket(msg); + protocolConnection->resumeWork(); + } + } + }; + g_dispatcher().addTask(createTask(callback)); + return true; +} + +bool Protocol::onRecvMessage(NetworkMessage& msg) +{ + if (checksumMethod != CHECKSUM_METHOD_NONE) { + uint32_t recvChecksum = msg.get(); + if (checksumMethod == CHECKSUM_METHOD_SEQUENCE) { + if (recvChecksum == 0) { + // checksum 0 indicate that the packet should be connection ping - 0x1C packet header + // since we don't need that packet skip it + return false; + } + + uint32_t checksum; + checksum = ++clientSequenceNumber; + if (clientSequenceNumber >= 0x7FFFFFFF) { + clientSequenceNumber = 0; + } + + if (recvChecksum != checksum) { + // incorrect packet - skip it + return false; + } + } else { + uint32_t checksum; + if (int32_t len = msg.getLength() - msg.getBufferPosition(); + len > 0) + { + checksum = adlerChecksum(msg.getBuffer() + msg.getBufferPosition(), len); + } else { + checksum = 0; + } + + if (recvChecksum != checksum) { + // incorrect packet - skip it + return false; + } + } + } + + return sendRecvMessageCallback(msg); } OutputMessage_ptr Protocol::getOutputBuffer(int32_t size) @@ -64,27 +129,72 @@ OutputMessage_ptr Protocol::getOutputBuffer(int32_t size) void Protocol::XTEA_encrypt(OutputMessage& msg) const { + const uint32_t delta = 0x61C88647; + // The message must be a multiple of 8 - size_t paddingBytes = msg.getLength() % 8u; + size_t paddingBytes = msg.getLength() & 7; if (paddingBytes != 0) { msg.addPaddingBytes(8 - paddingBytes); } uint8_t* buffer = msg.getOutputBuffer(); - xtea::encrypt(buffer, msg.getLength(), key); + auto messageLength = static_cast(msg.getLength()); + int32_t readPos = 0; + const std::array newKey = {key[0], key[1], key[2], key[3]}; + // TODO: refactor this for not use c-style + uint32_t precachedControlSum[32][2]; + uint32_t sum = 0; + for (int32_t i = 0; i < 32; ++i) { + precachedControlSum[i][0] = (sum + newKey[sum & 3]); + sum -= delta; + precachedControlSum[i][1] = (sum + newKey[(sum >> 11) & 3]); + } + while (readPos < messageLength) { + std::array vData = {}; + memcpy(vData.data(), buffer + readPos, 8); + for (int32_t i = 0; i < 32; ++i) { + vData[0] += ((vData[1] << 4 ^ vData[1] >> 5) + vData[1]) ^ precachedControlSum[i][0]; + vData[1] += ((vData[0] << 4 ^ vData[0] >> 5) + vData[0]) ^ precachedControlSum[i][1]; + } + memcpy(buffer + readPos, vData.data(), 8); + readPos += 8; + } } bool Protocol::XTEA_decrypt(NetworkMessage& msg) const { - if (((msg.getLength() - 6) & 7) != 0) { + uint16_t msgLength = msg.getLength() - (checksumMethod == CHECKSUM_METHOD_NONE ? 2 : 6); + if ((msgLength & 7) != 0) { return false; } + const uint32_t delta = 0x61C88647; + uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition(); - xtea::decrypt(buffer, msg.getLength() - 6, key); + auto messageLength = static_cast(msgLength); + int32_t readPos = 0; + const std::array newKey = {key[0], key[1], key[2], key[3]}; + // TODO: refactor this for not use c-style + uint32_t precachedControlSum[32][2]; + uint32_t sum = 0xC6EF3720; + for (int32_t i = 0; i < 32; ++i) { + precachedControlSum[i][0] = (sum + newKey[(sum >> 11) & 3]); + sum += delta; + precachedControlSum[i][1] = (sum + newKey[sum & 3]); + } + while (readPos < messageLength) { + std::array vData = {}; + memcpy(vData.data(), buffer + readPos, 8); + for (int32_t i = 0; i < 32; ++i) { + vData[1] -= ((vData[0] << 4 ^ vData[0] >> 5) + vData[0]) ^ precachedControlSum[i][0]; + vData[0] -= ((vData[1] << 4 ^ vData[1] >> 5) + vData[1]) ^ precachedControlSum[i][1]; + } + memcpy(buffer + readPos, vData.data(), 8); + readPos += 8; + } uint16_t innerLength = msg.get(); - if (innerLength + 8 > msg.getLength()) { + if (innerLength > msgLength - 2) { return false; } @@ -98,15 +208,63 @@ bool Protocol::RSA_decrypt(NetworkMessage& msg) return false; } - g_RSA().decrypt(msg.getBuffer() + msg.getBufferPosition()); //does not break strict aliasing - return msg.getByte() == 0; + auto charData = static_cast(static_cast(msg.getBuffer())); + // Does not break strict aliasing + g_RSA().decrypt(charData + msg.getBufferPosition()); + return (msg.getByte() == 0); } uint32_t Protocol::getIP() const { - if (auto conn = getConnection()) { - return conn->getIP(); + auto protocolConnection = getConnection(); + if (protocolConnection == nullptr) { + SPDLOG_ERROR("[Protocol::getIP] - Connection is nullptr"); + return 0; + } + + return protocolConnection->getIP(); +} + +void Protocol::enableCompression() +{ + if (!compreesionEnabled) { + int32_t compressionLevel = g_configManager().getNumber(COMPRESSION_LEVEL); + if (compressionLevel != 0) { + defStream.reset(new z_stream); + defStream->zalloc = Z_NULL; + defStream->zfree = Z_NULL; + defStream->opaque = Z_NULL; + if (deflateInit2(defStream.get(), compressionLevel, Z_DEFLATED, -15, 9, Z_DEFAULT_STRATEGY) != Z_OK) { + defStream.reset(); + SPDLOG_ERROR("[Protocol::enableCompression()] - Zlib deflateInit2 error: {}", (defStream->msg ? defStream->msg : " unknown error")); + } else { + compreesionEnabled = true; + } + } + } +} + +bool Protocol::compression(OutputMessage& msg) const +{ + static thread_local std::array defBuffer; + defStream->next_in = msg.getOutputBuffer(); + defStream->avail_in = msg.getLength(); + defStream->next_out = defBuffer.data(); + defStream->avail_out = NETWORKMESSAGE_MAXSIZE; + + if (int32_t ret = deflate(defStream.get(), Z_FINISH); + ret != Z_OK && ret != Z_STREAM_END) + { + return false; + } + auto totalSize = static_cast(defStream->total_out); + deflateReset(defStream.get()); + if (totalSize == 0) { + return false; } - return 0; + msg.reset(); + auto charData = static_cast(static_cast(defBuffer.data())); + msg.addBytes(charData, static_cast(totalSize)); + return true; } diff --git a/src/server/network/protocol/protocol.h b/src/server/network/protocol/protocol.h index 09068ba5402..2ca8690ac5e 100644 --- a/src/server/network/protocol/protocol.h +++ b/src/server/network/protocol/protocol.h @@ -20,14 +20,16 @@ #ifndef SRC_SERVER_NETWORK_PROTOCOL_PROTOCOL_H_ #define SRC_SERVER_NETWORK_PROTOCOL_PROTOCOL_H_ +#include + #include "server/network/connection/connection.h" -#include "security/xtea.h" +#include "config/configmanager.h" class Protocol : public std::enable_shared_from_this { public: explicit Protocol(Connection_ptr initConnection) : connection(initConnection) {} - virtual ~Protocol() = default; + virtual ~Protocol(); // non-copyable Protocol(const Protocol&) = delete; @@ -36,7 +38,8 @@ class Protocol : public std::enable_shared_from_this virtual void parsePacket(NetworkMessage&) {} virtual void onSendMessage(const OutputMessage_ptr& msg); - void onRecvMessage(NetworkMessage& msg); + bool onRecvMessage(NetworkMessage& msg); + bool sendRecvMessageCallback(NetworkMessage& msg); virtual void onRecvFirstMessage(NetworkMessage& msg) = 0; virtual void onConnect() {} @@ -58,32 +61,23 @@ class Protocol : public std::enable_shared_from_this } void send(OutputMessage_ptr msg) const { - if (auto conn = getConnection()) { - conn->send(msg); - } + getConnection()->send(msg); } protected: void disconnect() const { - if (auto conn = getConnection()) { - conn->close(); - } + getConnection()->close(); } void enableXTEAEncryption() { encryptionEnabled = true; } - void setXTEAKey(xtea::key key) { - this->key = std::move(key); - } - void disableChecksum() { - checksumEnabled = false; - } - void enableCompact() { - compactCrypt = true; + void setXTEAKey(const uint32_t* newKey) { + memcpy(this->key.data(), newKey, sizeof(*newKey) * 4); } - bool isCompact() { - return compactCrypt; + void setChecksumMethod(ChecksumMethods_t method) { + checksumMethod = method; } + void enableCompression(); static bool RSA_decrypt(NetworkMessage& msg); @@ -96,18 +90,21 @@ class Protocol : public std::enable_shared_from_this private: void XTEA_encrypt(OutputMessage& msg) const; bool XTEA_decrypt(NetworkMessage& msg) const; + bool compression(OutputMessage& msg) const; friend class Connection; OutputMessage_ptr outputBuffer; + std::unique_ptr defStream; const ConnectionWeak_ptr connection; - xtea::key key; - uint32_t sequenceNumber = 0; + std::array key = {}; + uint32_t serverSequenceNumber = 0; + uint32_t clientSequenceNumber = 0; + std::underlying_type_t checksumMethod = CHECKSUM_METHOD_NONE; bool encryptionEnabled = false; - bool checksumEnabled = true; - bool compactCrypt = false; bool rawMessages = false; + bool compreesionEnabled = false; }; #endif // SRC_SERVER_NETWORK_PROTOCOL_PROTOCOL_H_ diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index da412b81df6..432d53a80b2 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -431,10 +431,13 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage &msg) } OperatingSystem_t operatingSystem = static_cast(msg.get()); - if (operatingSystem <= CLIENTOS_NEW_MAC) { - enableCompact(); + setChecksumMethod(CHECKSUM_METHOD_SEQUENCE); + enableCompression(); + } else { + setChecksumMethod(CHECKSUM_METHOD_ADLER32); } + version = msg.get(); // Protocol version @@ -451,13 +454,9 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage &msg) return; } - xtea::key key; - key[0] = msg.get(); - key[1] = msg.get(); - key[2] = msg.get(); - key[3] = msg.get(); + std::array key = {msg.get(), msg.get(), msg.get(), msg.get()}; enableXTEAEncryption(); - setXTEAKey(std::move(key)); + setXTEAKey(key.data()); msg.skipBytes(1); // gamemaster flag diff --git a/src/server/network/protocol/protocollogin.cpp b/src/server/network/protocol/protocollogin.cpp index 2532289d8ee..f986e06be11 100644 --- a/src/server/network/protocol/protocollogin.cpp +++ b/src/server/network/protocol/protocollogin.cpp @@ -118,20 +118,17 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) return; } - OperatingSystem_t operatingSystem = static_cast(msg.get()); - - if (operatingSystem <= CLIENTOS_NEW_MAC) - enableCompact(); + msg.skipBytes(2); // client OS uint16_t version = msg.get(); msg.skipBytes(17); /* - * Skipped bytes: - * 4 bytes: client version - * 12 bytes: dat, spr, pic signatures (4 bytes each) - * 1 byte: 0 - */ + - Skipped bytes: + - 4 bytes: client version (971+) + - 12 bytes: dat, spr, pic signatures (4 bytes each) + - 1 byte: preview world(971+) + */ if (!Protocol::RSA_decrypt(msg)) { SPDLOG_WARN("[ProtocolLogin::onRecvFirstMessage] - RSA Decrypt Failed"); @@ -139,13 +136,11 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) return; } - xtea::key key; - key[0] = msg.get(); - key[1] = msg.get(); - key[2] = msg.get(); - key[3] = msg.get(); + std::array key = {msg.get(), msg.get(), msg.get(), msg.get()}; enableXTEAEncryption(); - setXTEAKey(std::move(key)); + setXTEAKey(key.data()); + + setChecksumMethod(CHECKSUM_METHOD_ADLER32); if (g_game().getGameState() == GAME_STATE_STARTUP) { disconnectClient("Gameworld is starting up. Please wait.", version); diff --git a/src/server/server.cpp b/src/server/server.cpp index 7672bcc1fe0..1a38b863ed4 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -54,7 +54,7 @@ void ServiceManager::stop() for (auto& servicePortIt : acceptors) { try { io_service.post(std::bind(&ServicePort::onStopServer, servicePortIt.second)); - } catch (boost::system::system_error& e) { + } catch (const boost::system::system_error& e) { SPDLOG_WARN("[ServiceManager::stop] - Network error: {}", e.what()); } } @@ -183,7 +183,7 @@ void ServicePort::open(uint16_t port) acceptor->set_option(boost::asio::ip::tcp::no_delay(true)); accept(); - } catch (boost::system::system_error& e) { + } catch (const boost::system::system_error& e) { SPDLOG_WARN("[ServicePort::open] - Error: {}", e.what()); pendingStart = true; diff --git a/src/server/server_definitions.hpp b/src/server/server_definitions.hpp index 722f28f4e34..20b1c8d6727 100644 --- a/src/server/server_definitions.hpp +++ b/src/server/server_definitions.hpp @@ -29,12 +29,11 @@ enum {XTEA_MULTIPLE = 8}; enum {MAX_BODY_LENGTH = NETWORKMESSAGE_MAXSIZE - HEADER_LENGTH - CHECKSUM_LENGTH - XTEA_MULTIPLE}; enum {MAX_PROTOCOL_BODY_LENGTH = MAX_BODY_LENGTH - 10}; -enum ConnectionState_t : int8_t { - CONNECTION_STATE_DISCONNECTED, - CONNECTION_STATE_CONNECTING_STAGE1, - CONNECTION_STATE_CONNECTING_STAGE2, - CONNECTION_STATE_GAME, - CONNECTION_STATE_PENDING +enum ConnectionState_t : uint8_t { + CONNECTION_STATE_OPEN, + CONNECTION_STATE_IDENTIFYING, + CONNECTION_STATE_READINGS, + CONNECTION_STATE_CLOSED }; // Connection and networkmessage. @@ -50,6 +49,12 @@ enum RequestedInfo_t : uint16_t { REQUEST_SERVER_SOFTWARE_INFO = 1 << 7, }; +enum ChecksumMethods_t : uint8_t { + CHECKSUM_METHOD_NONE, + CHECKSUM_METHOD_ADLER32, + CHECKSUM_METHOD_SEQUENCE +}; + enum SessionEndInformations : uint8_t { // Guessing unknown types are ban/protocol error or something. // But since there aren't any difference from logout should we care? diff --git a/src/utils/const.hpp b/src/utils/const.hpp index a79929f9135..aeee76b1d3b 100644 --- a/src/utils/const.hpp +++ b/src/utils/const.hpp @@ -24,7 +24,11 @@ const uint32_t MAX_LOOTCHANCE = 100000; const uint32_t MAX_STATICWALK = 100; static constexpr size_t NETWORKMESSAGE_PLAYERNAME_MAXLENGTH = 30; -static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590; +static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 65500; + +// QT clients probably have bigger input buffer because of exiva options +// But for now we don't support exiva options +static constexpr int32_t INPUTMESSAGE_MAXSIZE = 2048; static constexpr int32_t CHANNEL_GUILD = 0x00; static constexpr int32_t CHANNEL_PARTY = 0x01; diff --git a/src/utils/simd.hpp b/src/utils/simd.hpp new file mode 100644 index 00000000000..a6472671e79 --- /dev/null +++ b/src/utils/simd.hpp @@ -0,0 +1,81 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (C) 2021 OpenTibiaBR + * Copyright (C) 2019-2021 Saiyans King + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef SRC_UTILS_SIMD_HPP_ +#define SRC_UTILS_SIMD_HPP_ + +//#define __DISABLE_VECTORIZATION__ 1 + +#if defined(__DISABLE_VECTORIZATION__) +//You might want to disable vectorization on some compilers +//it can just get buggy and the engine will crashes +#undef __NEON__ +#undef __ARM_NEON__ +#undef __ARM_FEATURE_SIMD32 +#undef __SSE__ +#undef __SSE2__ +#undef __SSE3__ +#undef __SSSE3__ +#undef __SSE4_1__ +#undef __SSE4_2__ +#undef __AVX__ +#undef __AVX2__ +#undef __AVX512F__ +#else +#if defined(__ARM_NEON__) || defined(__ARM_FEATURE_SIMD32) +#define __NEON__ 1 +#include +#endif +#if defined(__SSE__) +#include +#endif +#if defined(__SSE2__) +#include +#endif +#if defined(__SSE3__) +#include +#endif +#if defined(__SSSE3__) +#include +#endif +#if defined(__SSE4_1__) +#include +#endif +#if defined(__SSE4_2__) +#include +#endif +#if defined(__AVX__) || defined(__AVX2__) || defined(__AVX512F__) +#include +#endif +#endif + +#ifdef _MSC_VER +#include +__forceinline unsigned int _mm_ctz(unsigned int value) +{ + unsigned long i = 0; + _BitScanForward(&i, value); + return static_cast(i); +} +#else +#define _mm_ctz __builtin_ctz +#endif + +#endif // SRC_UTILS_SIMD_HPP_ diff --git a/vcpkg.json b/vcpkg.json index f9bddccd13e..cb262e036dd 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -8,19 +8,26 @@ "boost-filesystem", "boost-iostreams", "boost-system", - { "name": "libmariadb", - "features": [ "mariadbclient" ] - }, "pugixml", "spdlog", "curl", "jsoncpp", - "cryptopp", "libzippp", + "protobuf", + { "name": "libmariadb", + "features": [ "mariadbclient" ] + }, { "name": "luajit", "platform": "windows" }, - "protobuf" + { + "name": "gmp", + "platform": "linux" + }, + { + "name": "mpir", + "platform": "windows" + } ] }