/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see .
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "Http.h"
#include "Network/network.hpp"
#include "Security/Init.h"
#include
#include
#if defined(_WIN32)
#include
#include
#elif defined(__linux__)
#include
#include
#include
#include
#include
#include
#include
#include
#endif
#include "Logger.h"
#include "Startup.h"
#include
#include
#include
#include
#include
#include "Options.h"
#include
extern int TraceBack;
std::set* ConfList = nullptr;
bool TCPTerminate = false;
bool Terminate = false;
bool LoginAuth = false;
std::string Username = "";
std::string UserRole = "";
int UserID = -1;
std::string UlStatus;
std::string MStatus;
bool ModLoaded;
int ping = -1;
SOCKET CoreSocket = -1;
signed char confirmed = -1;
bool SecurityWarning() {
confirmed = -1;
CoreSend("WMODS_FOUND");
while (confirmed == -1)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (confirmed == 1)
return true;
NetReset();
Terminate = true;
TCPTerminate = true;
ping = -1;
return false;
}
void StartSync(const std::string& Data) {
std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1));
if (IP.find('.') == -1) {
if (IP == "DNS")
UlStatus = "UlConnection Failed! (DNS Lookup Failed)";
else
UlStatus = "UlConnection Failed! (WSA failed to start)";
Terminate = true;
CoreSend("L");
return;
}
CheckLocalKey();
UlStatus = "UlLoading...";
TCPTerminate = false;
Terminate = false;
ConfList->clear();
ping = -1;
std::thread GS(TCPGameServer, IP, std::stoi(Data.substr(Data.find(':') + 1)));
GS.detach();
info("Connecting to server");
}
void GetServerInfo(std::string Data) {
debug("Fetching server info of " + Data.substr(1));
std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1));
if (IP.find('.') == -1) {
if (IP == "DNS")
warn("Connection Failed! (DNS Lookup Failed) for " + Data);
else
warn("Connection Failed! (WSA failed to start) for " + Data);
CoreSend("I" + Data + ";");
return;
}
SOCKET ISock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN ServerAddr;
if (ISock < 1) {
debug("Socket creation failed with error: " + std::to_string(WSAGetLastError()));
KillSocket(ISock);
CoreSend("I" + Data + ";");
return;
}
ServerAddr.sin_family = AF_INET;
int port = std::stoi(Data.substr(Data.find(':') + 1));
if (port < 1 || port > 65535) {
debug("Invalid port number: " + std::to_string(port));
KillSocket(ISock);
CoreSend("I" + Data + ";");
return;
}
ServerAddr.sin_port = htons(port);
inet_pton(AF_INET, IP.c_str(), &ServerAddr.sin_addr);
if (connect(ISock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)) != 0) {
debug("Connection to server failed with error: " + std::to_string(WSAGetLastError()));
KillSocket(ISock);
CoreSend("I" + Data + ";");
return;
}
char Code[1] = { 'I' };
if (send(ISock, Code, 1, 0) != 1) {
debug("Sending data to server failed with error: " + std::to_string(WSAGetLastError()));
KillSocket(ISock);
CoreSend("I" + Data + ";");
return;
}
const std::string buffer = ([&]() -> std::string {
int32_t Header;
std::vector data(sizeof(Header));
int Temp = recv(ISock, data.data(), sizeof(Header), MSG_WAITALL);
auto checkBytes = ([&](const int32_t bytes) -> bool {
if (bytes == 0) {
return false;
} else if (bytes < 0) {
return false;
}
return true;
});
if (!checkBytes(Temp)) {
return "";
}
memcpy(&Header, data.data(), sizeof(Header));
if (!checkBytes(Temp)) {
return "";
}
data.resize(Header, 0);
Temp = recv(ISock, data.data(), Header, MSG_WAITALL);
if (!checkBytes(Temp)) {
return "";
}
return std::string(data.data(), Header);
})();
if (!buffer.empty()) {
debug("Server Info: " + buffer);
CoreSend("I" + Data + ";" + buffer);
} else {
debug("Receiving data from server failed with error: " + std::to_string(WSAGetLastError()));
debug("Failed to receive server info from " + Data);
CoreSend("I" + Data + ";");
}
KillSocket(ISock);
}
std::mutex sendMutex;
void CoreSend(std::string data) {
std::lock_guard lock(sendMutex);
if (CoreSocket != -1) {
int res = send(CoreSocket, (data + "\n").c_str(), int(data.size()) + 1, 0);
if (res < 0) {
debug("(Core) send failed with error: " + std::to_string(WSAGetLastError()));
}
}
}
bool IsAllowedLink(const std::string& Link) {
std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|beammp\.gg|github\.com\/BeamMP\/|discord\.gg|patreon\.com\/BeamMP))");
std::smatch link_match;
return std::regex_search(Link, link_match, link_pattern) && link_match.position() == 0;
}
std::vector> futures;
void Parse(std::string Data, SOCKET CSocket) {
std::erase_if(futures, [](const std::future& f) {
if (f.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
return true;
}
return false;
});
char Code = Data.at(0), SubCode = 0;
if (Data.length() > 1)
SubCode = Data.at(1);
switch (Code) {
case 'A':
Data = Data.substr(0, 1);
break;
case 'B': {
NetReset();
Terminate = true;
TCPTerminate = true;
Data.clear();
futures.push_back(std::async(std::launch::async, []() {
CoreSend("B" + HTTP::Get("https://backend.beammp.com/servers-info"));
}));
}
break;
case 'C':
StartSync(Data);
Data.clear();
break;
case 'O': // open default browser with URL
if (IsAllowedLink(Data.substr(1))) {
#if defined(__linux)
if (char* browser = getenv("BROWSER"); browser != nullptr && !std::string_view(browser).empty()) {
pid_t pid;
auto arg = Data.substr(1);
char* argv[] = { browser, arg.data() };
auto status = posix_spawn(&pid, browser, nullptr, nullptr, argv, environ);
if (status == 0) {
debug("Browser PID: " + std::to_string(pid));
// we don't wait for it to exit, because we just don't care.
// typically, you'd waitpid() here.
} else {
error("Failed to open the following link in the browser (error follows below): " + arg);
error(std::string("posix_spawn: ") + strerror(status));
}
} else {
error("Failed to open the following link in the browser because the $BROWSER environment variable is not set: " + Data.substr(1));
}
#elif defined(WIN32)
ShellExecuteA(nullptr, "open", Data.substr(1).c_str(), nullptr, nullptr, SW_SHOW); /// TODO: Look at when working on linux port
#endif
info("Opening Link \"" + Data.substr(1) + "\"");
}
Data.clear();
break;
case 'P':
Data = Code + std::to_string(ProxyPort);
break;
case 'U':
if (SubCode == 'l')
Data = UlStatus;
if (SubCode == 'p') {
if (ping > 800) {
Data = "Up-2";
} else
Data = "Up" + std::to_string(ping);
}
if (!SubCode) {
std::string Ping;
if (ping > 800)
Ping = "-2";
else
Ping = std::to_string(ping);
Data = std::string(UlStatus) + "\n" + "Up" + Ping;
}
break;
case 'M':
Data = MStatus;
break;
case 'Q':
if (SubCode == 'S') {
NetReset();
Terminate = true;
TCPTerminate = true;
ping = -1;
}
if (SubCode == 'G') {
debug("Closing via 'G' packet");
exit(2);
}
Data.clear();
break;
case 'R': // will send mod name
if (ConfList->find(Data) == ConfList->end()) {
ConfList->insert(Data);
ModLoaded = true;
}
Data.clear();
break;
case 'Z':
Data = "Z" + GetVer();
break;
case 'N':
if (SubCode == 'c') {
nlohmann::json Auth = {
{ "Auth", LoginAuth ? 1 : 0 },
};
if (!Username.empty()) {
Auth["username"] = Username;
}
if (!UserRole.empty()) {
Auth["role"] = UserRole;
}
if (UserID != -1) {
Auth["id"] = UserID;
}
Data = "N" + Auth.dump();
} else {
futures.push_back(std::async(std::launch::async, [data = std::move(Data)]() {
CoreSend("N" + Login(data.substr(data.find(':') + 1)));
}));
Data.clear();
}
break;
case 'W':
if (SubCode == 'Y') {
confirmed = 1;
} else if (SubCode == 'N') {
confirmed = 0;
}
Data.clear();
break;
case 'I': {
auto future = std::async(std::launch::async, [data = std::move(Data)]() {
GetServerInfo(data);
});
break;
}
default:
Data.clear();
break;
}
if (!Data.empty())
CoreSend(Data);
}
void GameHandler(SOCKET Client) {
CoreSocket = Client;
int32_t Size, Rcv;
int Temp;
char Header[10] = { 0 };
do {
Rcv = 0;
do {
Temp = recv(Client, &Header[Rcv], 1, 0);
if (Temp < 1)
break;
if (!isdigit(Header[Rcv]) && Header[Rcv] != '>') {
error("(Core) Invalid lua communication");
KillSocket(Client);
return;
}
} while (Header[Rcv++] != '>');
if (Temp < 1)
break;
if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') {
debug("(Core) Invalid lua Header -> " + std::string(Header, Rcv));
break;
}
std::string Ret(Size, 0);
Rcv = 0;
do {
Temp = recv(Client, &Ret[Rcv], Size - Rcv, 0);
if (Temp < 1)
break;
Rcv += Temp;
} while (Rcv < Size);
if (Temp < 1)
break;
Parse(Ret, Client);
} while (Temp > 0);
if (Temp == 0) {
debug("(Core) Connection closing");
} else {
debug("(Core) recv failed with error: " + std::to_string(WSAGetLastError()));
}
NetReset();
KillSocket(Client);
}
void localRes() {
MStatus = " ";
UlStatus = "Ulstart";
if (ConfList != nullptr) {
ConfList->clear();
delete ConfList;
ConfList = nullptr;
}
ConfList = new std::set;
}
void CoreMain() {
debug("Core Network on start! port: " + std::to_string(options.port));
SOCKET LSocket, CSocket;
struct addrinfo* res = nullptr;
struct addrinfo hints { };
int iRes;
#ifdef _WIN32
WSADATA wsaData;
iRes = WSAStartup(514, &wsaData); // 2.2
if (iRes)
debug("WSAStartup failed with error: " + std::to_string(iRes));
#endif
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
iRes = getaddrinfo("127.0.0.1", std::to_string(options.port).c_str(), &hints, &res);
if (iRes) {
debug("(Core) addr info failed with error: " + std::to_string(iRes));
WSACleanup();
return;
}
LSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (LSocket == -1) {
debug("(Core) socket failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(res);
WSACleanup();
return;
}
iRes = bind(LSocket, res->ai_addr, int(res->ai_addrlen));
if (iRes == SOCKET_ERROR) {
error("(Core) bind failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(res);
KillSocket(LSocket);
WSACleanup();
return;
}
iRes = listen(LSocket, SOMAXCONN);
if (iRes == SOCKET_ERROR) {
debug("(Core) listen failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(res);
KillSocket(LSocket);
WSACleanup();
return;
}
do {
CSocket = accept(LSocket, nullptr, nullptr);
if (CSocket == -1) {
error("(Core) accept failed with error: " + std::to_string(WSAGetLastError()));
continue;
}
localRes();
info("Game Connected!");
GameHandler(CSocket);
warn("Game Reconnecting...");
} while (CSocket);
KillSocket(LSocket);
WSACleanup();
}
#if defined(_WIN32)
int Handle(EXCEPTION_POINTERS* ep) {
char* hex = new char[100];
sprintf_s(hex, 100, "%lX", ep->ExceptionRecord->ExceptionCode);
except("(Core) Code : " + std::string(hex));
delete[] hex;
return 1;
}
#endif
[[noreturn]] void CoreNetwork() {
while (true) {
#if not defined(__MINGW32__)
__try {
#endif
CoreMain();
#if not defined(__MINGW32__) and not defined(__linux__)
} __except (Handle(GetExceptionInformation())) { }
#elif not defined(__MINGW32__) and defined(__linux__)
}
catch (...) {
except("(Core) Code : " + std::string(strerror(errno)));
}
#endif
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}