10 Commits

13 changed files with 317 additions and 255 deletions

View File

@ -15,4 +15,7 @@ gamefiles/data/quest/pre_qc/
gamefiles/data/quest/qc
# Test folder
test/
test/
# Built files
build/

View File

@ -9,7 +9,7 @@ RUN echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https:
# Update the system and install various dependencies
RUN apt-get update && \
apt-get install -y git cmake ninja-build build-essential tar curl zip unzip pkg-config autoconf python3 \
libdevil-dev libncurses5-dev libbsd-dev
libdevil-dev libncurses5-dev libbsd-dev
# Arm specific
ENV VCPKG_FORCE_SYSTEM_BINARIES=1
@ -17,7 +17,7 @@ ENV VCPKG_FORCE_SYSTEM_BINARIES=1
# Install vcpkg and the required libraries
RUN git clone https://github.com/Microsoft/vcpkg.git
RUN bash ./vcpkg/bootstrap-vcpkg.sh
RUN ./vcpkg/vcpkg install boost-system cryptopp effolkronium-random libmysql libevent lzo fmt spdlog
RUN ./vcpkg/vcpkg install boost-system cryptopp effolkronium-random libmysql libevent lzo fmt spdlog argon2
COPY . .
@ -29,7 +29,7 @@ RUN cd build && make -j $(nproc)
FROM ubuntu:22.04 as app
WORKDIR /app
RUN apt-get update && apt-get install -y python2 libdevil-dev libbsd-dev && apt-get clean
RUN apt-get update && apt-get install -y gettext python2 libdevil-dev libbsd-dev && apt-get clean
# Copy the binaries from the build stage
COPY --from=build /app/build/src/db/db /bin/db
@ -39,6 +39,9 @@ COPY --from=build /app/build/src/quest/qc /bin/qc
# Copy the game files
COPY ./gamefiles/ .
# Copy the auxiliary files
COPY ./docker/ .
# Compile the quests
RUN cd /app/data/quest && python2 make.py
@ -48,3 +51,9 @@ RUN ln -s ./conf/item_names_en.txt item_names.txt
RUN ln -s ./conf/item_proto.txt item_proto.txt
RUN ln -s ./conf/mob_names_en.txt mob_names.txt
RUN ln -s ./conf/mob_proto.txt mob_proto.txt
# Set up default environment variables
ENV PUBLIC_BIND_IP 0.0.0.0
ENV INTERNAL_BIND_IP 0.0.0.0
ENTRYPOINT ["/usr/bin/bash", "docker-entrypoint.sh"]

View File

@ -35,11 +35,11 @@ Also install DevIL (1.7.8) and the BSD compatibility library:
apt-get install -y libdevil-dev libbsd-dev
```
Install `vcpkg` according to the [lastest instructions](https://vcpkg.io/en/getting-started.html).
Install `vcpkg` according to the [latest instructions](https://vcpkg.io/en/getting-started.html).
Build and install the required libraries:
```shell
vcpkg install boost-system cryptopp effolkronium-random libmysql libevent lzo fmt spdlog
vcpkg install boost-system cryptopp effolkronium-random libmysql libevent lzo fmt spdlog argon2
```
Then, it's time to build your binaries. Your commands should look along the lines of:
@ -79,8 +79,9 @@ goodies you wish. Also, a lot of time.
- Removed unused functionalities (time bombs, activation servers, other Korean stuff)
- Switched to the [effolkronium/random PRNG](https://github.com/effolkronium/random) instead of the standard C functions.
- Refactored macros to modern C++ functions.
- Network settings are manually configurable through the `PUBLIC_IP`, `PUBLIC_BIND_IP`, `INTERNAL_IP`, `INTERNAL_BIND_IP` settings in the `CONFIG` file. (Might need further work)
- Network settings are manually configurable through the `PUBLIC_IP`, `PUBLIC_BIND_IP`, `INTERNAL_IP`, `INTERNAL_BIND_IP` settings in the `game.conf` file. (Might need further work)
- Refactored logging to use [spdlog](https://github.com/gabime/spdlog) for more consistent function calls.
- Refactored login code to use Argon2ID.
## 4. Bugfixes
**WARNING: This project is based on the "kraizy" leak. That was over 10 years ago.
@ -101,7 +102,7 @@ This is a very serious security risk and one of the reasons this project is stil
- Fixed buffer overruns and integer overflows in SQL queries.
## 5. Further plans
- Migrate `conf.txt` and `CONFIG` to a modern dotenv-like format, which would enable pretty nice Docker images.
- Migrate `db.conf` and `game.conf` to a modern dotenv-like format, which would enable pretty nice Docker images.
- Add a health check to the Docker image.
- Use the [fmt](https://fmt.dev/latest/index.html) library for safe and modern string formatting.
- Handle kernel signals (SIGTERM, SIGHUP etc.) for gracefully shutting down the game server.

18
docker/conf/db.conf.tmpl Normal file
View File

@ -0,0 +1,18 @@
WELCOME_MSG = "DB Server has been started"
SQL_ACCOUNT = "${MYSQL_HOST} ${MYSQL_DB_ACCOUNT} ${MYSQL_USER} ${MYSQL_PASSWORD} ${MYSQL_PORT}"
SQL_COMMON = "${MYSQL_HOST} ${MYSQL_DB_COMMON} ${MYSQL_USER} ${MYSQL_PASSWORD} ${MYSQL_PORT}"
SQL_PLAYER = "${MYSQL_HOST} ${MYSQL_DB_PLAYER} ${MYSQL_USER} ${MYSQL_PASSWORD} ${MYSQL_PORT}"
TABLE_POSTFIX = ""
BIND_PORT = ${DB_PORT}
DB_SLEEP_MSEC = 10
CLIENT_HEART_FPS = 10
HASH_PLAYER_LIFE_SEC = 600
BACKUP_LIMIT_SEC = 3600
PLAYER_ID_START = 100
PLAYER_DELETE_LEVEL_LIMIT = 70
ITEM_ID_RANGE = 10000001 20000000
TEST_SERVER = ${TEST_SERVER}

View File

@ -0,0 +1,41 @@
HOSTNAME: ${GAME_HOSTNAME}
CHANNEL: ${GAME_CHANNEL}
PUBLIC_IP: ${PUBLIC_IP}
PUBLIC_BIND_IP: ${PUBLIC_BIND_IP}
INTERNAL_IP: ${INTERNAL_IP}
INTERNAL_BIND_IP: ${INTERNAL_BIND_IP}
PORT: ${GAME_PORT}
P2P_PORT: ${GAME_P2P_PORT}
DB_ADDR: ${DB_ADDR}
DB_PORT: ${DB_PORT}
COMMON_SQL: ${MYSQL_HOST} ${MYSQL_USER} ${MYSQL_PASSWORD} ${MYSQL_DB_COMMON} ${MYSQL_PORT}
LOG_SQL: ${MYSQL_HOST} ${MYSQL_USER} ${MYSQL_PASSWORD} ${MYSQL_DB_LOG} ${MYSQL_PORT}
PLAYER_SQL: ${MYSQL_HOST} ${MYSQL_USER} ${MYSQL_PASSWORD} ${MYSQL_DB_PLAYER} ${MYSQL_PORT}
TABLE_POSTFIX:
AUTH_SERVER: ${GAME_AUTH_SERVER}
MARK_SERVER: ${GAME_MARK_SERVER}
MAP_ALLOW: ${GAME_MAP_ALLOW}
MAX_LEVEL: ${GAME_MAX_LEVEL}
PK_PROTECT_LEVEL: 15
SPAM_BLOCK_MAX_LEVEL: 90
GOLD_DROP_LIMIT_TIME: 10
MALL_URL: ${GAME_MALL_URL}
CHECK_MULTIHACK: 0
SPEEDHACK_LIMIT_COUNT: 300
SPEEDHACK_LIMIT_BONUS: 80
ADMINPAGE_IP: 127.0.0.1
ADMINPAGE_PASSWORD: metin2adminpass
PASSES_PER_SEC: 25
SAVE_EVENT_SECOND_CYCLE: 180
PING_EVENT_SECOND_CYCLE: 180
VIEW_RANGE: 8000
TEST_SERVER: ${TEST_SERVER}

View File

@ -0,0 +1,9 @@
#!/bin/sh
# docker-entrypoint.sh
# Generate configuration files based on environment variables
envsubst <"/app/conf/db.conf.tmpl" >"/app/db.conf"
envsubst <"/app/conf/game.conf.tmpl" >"/app/game.conf"
# Run the standard container command.
exec "$@"

View File

@ -46,13 +46,13 @@ Mysql-Funktion der neuesten Generation.
--]]
mysql_query = function(query)
if not pre then
local rt = io.open('CONFIG','r'):read('*all')
local rt = io.open('game.conf','r'):read('*all')
pre,_= string.gsub(rt,'.+PLAYER_SQL:%s(%S+)%s(%S+)%s(%S+)%s(%S+).+','-h%1 -u%2 -p%3 -D%4')
end
math.randomseed(os.time())
local fi,t,out = 'mysql_data_'..math.random(10^9)+math.random(2^4,2^10),{},{}
--os.execute('mysql '..pre..' --e='..string.format('%q',query)..' > '..fi) -- f<>r MySQL51
os.execute('mysql '..pre..' -e'..string.format('%q',query)..' > '..fi) -- f<>r MySQL55
--os.execute('mysql '..pre..' --e='..string.format('%q',query)..' > '..fi) -- f<>r MySQL51
os.execute('mysql '..pre..' -e'..string.format('%q',query)..' > '..fi) -- f<>r MySQL55
for av in io.open(fi,'r'):lines() do table.insert(t,split(av,'\t')) end; os.remove(fi);
for i = 2, table.getn(t) do table.foreach(t[i],function(a,b)
out[i-1] = out[i-1] or {}
@ -100,7 +100,7 @@ function mysql_query_old(query,user,pass,db,ip)
os.remove(path)
if type(q.l[1]) ~= "table" then
return "ERROR"
--error("Fehler bei der MySQL Verbindung oder bei der R<>ckgabe! Abbruch!")
--error("Fehler bei der MySQL Verbindung oder bei der R<>ckgabe! Abbruch!")
end
local ix = 0
table.foreachi(q.l,function(i,l)
@ -115,7 +115,7 @@ function mysql_query_old(query,user,pass,db,ip)
end) end
end)
-- ENDE der eigentlichen MySQL-Funktion
-- START Zusatz: Hanashi-Kompatibilit<69>t & Fehlerbehandlung
-- START Zusatz: Hanashi-Kompatibilit<69>t & Fehlerbehandlung
q.out.__data = q.l[1]
setmetatable(q.out, { __index = function(a,b)
if type(b) == "number" then
@ -132,7 +132,7 @@ end
@name define
@author Mijago
@descr
Gibt die M<>glichkeit, globale Variablen zu definieren. Es k<>nnen auch Funktionen genutzt werden! Diese werden dann AUSGEF<45>HRT zur<75>ckgegeben!
Gibt die M<>glichkeit, globale Variablen zu definieren. Es k<>nnen auch Funktionen genutzt werden! Diese werden dann AUSGEF<45>HRT zur<75>ckgegeben!
--]]
_G.__data = {}
local meta = getmetatable(_G) or {}
@ -155,7 +155,7 @@ end
@name duration
@author Mijago
@descr
Gibt die verbleibende Zeit als String zur<75>ck.
Gibt die verbleibende Zeit als String zur<75>ck.
--]]
function duration(ipe)
local ipe,dat= ipe or 0,''
@ -183,7 +183,7 @@ end
@name is_number
@author Mijago
@descr
Pr<EFBFBD>ft, ob eine Variable eine Zahl ist.
Pr<EFBFBD>ft, ob eine Variable eine Zahl ist.
--]]
function is_number(var)
return (type(var) == "number")
@ -194,7 +194,7 @@ end
@name is_string
@author Mijago
@descr
Pr<EFBFBD>ft, ob eine Variable ein String ist.
Pr<EFBFBD>ft, ob eine Variable ein String ist.
--]]
function is_string(var)
return (type(var) == "string")
@ -205,7 +205,7 @@ end
@name is_table
@author Mijago
@descr
Pr<EFBFBD>ft, ob eine Variable eine Tabelle ist.
Pr<EFBFBD>ft, ob eine Variable eine Tabelle ist.
--]]
function is_table(var)
return (type(var) == "table")
@ -216,7 +216,7 @@ end
@name in_table
@author Mijago
@descr
Pr<EFBFBD>ft, ob eine Variablei in einer Tabelle ist.
Pr<EFBFBD>ft, ob eine Variablei in einer Tabelle ist.
Aufruf: in_table(var,table)
--]]
function in_table ( e, t )
@ -292,7 +292,7 @@ end
@name math.minmax
@author Mijago
@descr
Erm<EFBFBD>glicht die Angabe von min und max auf einmal
Erm<EFBFBD>glicht die Angabe von min und max auf einmal
--]]
math.minmax = function(min,num,max)
return math.min(math.max(num,min),max)
@ -304,8 +304,8 @@ end
@name n_input
@author Mijago
@descr
F<EFBFBD>r Inputs nur f<>r Zahlen.
Die Zahl ist IMMER positiv. Wenn sie nicht g<>ltig ist, ist sie 0.
F<EFBFBD>r Inputs nur f<>r Zahlen.
Die Zahl ist IMMER positiv. Wenn sie nicht g<>ltig ist, ist sie 0.
--]]
function n_input()
return math.abs(tonumber(input()) or 0)
@ -316,7 +316,7 @@ end
@name long_input
@author Mijago
@descr
Erm<EFBFBD>glicht es, l<>ngere Inputs zu nutzen.
Erm<EFBFBD>glicht es, l<>ngere Inputs zu nutzen.
--]]
function long_input()
local str,t = "",input()
@ -358,7 +358,7 @@ function select2(tab,...)
outputstr=outputstr..'sel = select("'..l..'"'
elseif outputcount == max and tablen > outputcount+incit then
if tablen ~= outputcount+incit+1 then
outputstr=outputstr..',"'..l..'","N<>chste Seite") + '..incit..' '
outputstr=outputstr..',"'..l..'","N<>chste Seite") + '..incit..' '
if nextc > 0 then
outputstr = outputstr..'end '
end
@ -392,7 +392,7 @@ end
@descr
Wie Select2:
Eine Tabelle oder eine Stringliste wird auf Seiten aufgeteilt.
Weiter, Zur<75>ck und Abbrechen (-1) Buttons.
Weiter, Zur<75>ck und Abbrechen (-1) Buttons.
--]]
function select3(...)
arg.n = nil
@ -438,7 +438,7 @@ function select3(...)
pe[i] = copy_tab(px[i])
local next,back,exit = 0,0,0
if i < table.getn(pe) and table.getn(pe) ~=1 then table.insert(pe[i],table.getn(pe[i])+1,'Weiter zu Seite '..(i+1)); next = table.getn(pe[i]) end
if i > 1 then table.insert(pe[i],table.getn(pe[i])+1,'Zur<EFBFBD>ck zu Seite '..(i-1)); back = table.getn(pe[i]) end
if i > 1 then table.insert(pe[i],table.getn(pe[i])+1,'Zur<EFBFBD>ck zu Seite '..(i-1)); back = table.getn(pe[i]) end
table.insert(pe[i],table.getn(pe[i])+1,'Abbruch'); exit = table.getn(pe[i])
if table.getn(pe) > 1 then
say('Seite '..i..' von '..table.getn(pe))
@ -564,7 +564,7 @@ zt.s_ms = function(s)
@name Autoumbruch in Say
@author Mijago
@descr
F<EFBFBD>gt die Funktion say2 an.
F<EFBFBD>gt die Funktion say2 an.
Mit ihr werden Texte automatisch umgebrochen.
--]]
function say2(str,dx)
@ -616,13 +616,14 @@ end
@author Mijago; Idee von Benhero
@needs mysql_query
@descr
Funktion zum <20>ndern des Nutzerpasswortes.
Funktion zum <20>ndern des Nutzerpasswortes.
Angabe des Accounts kann weggelassen werden, als Accountname oder als Account ID angegeben werden.
--]]
account = account or {}
function account.set_pw(pw,ac)
if pw == nil then error("Fehler... Passwort muss gesetzt werden!") end
local ac = ac or pc.get_account_id()
-- This is weird behaviour and might need to be changed if ever used.
if type(ac) == "string" then
mysql_query("UPDATE player.player,account.account SET account.password = password("..string.format('%q',pw)..") WHERE account.id = player.account_id and player.name = '"..ac.."' LIMIT 1")
elseif type(ac) == "number" then
@ -635,7 +636,7 @@ end
@name pc.check_inventory_place
@author Mijago
@descr
Checkt auf Freie Inventarpl<70>tze f<>r Items der gr<67><72>e X (H<>he).
Checkt auf Freie Inventarpl<70>tze f<>r Items der gr<67><72>e X (H<>he).
--]]
function pc.check_inventory_place(size)
if size <= 0 or size > 3 then
@ -663,7 +664,7 @@ end
@name do_for_other
@author Mijago
@descr
F<EFBFBD>hrt einen String als Luabefehle bei einem anderem User aus.
F<EFBFBD>hrt einen String als Luabefehle bei einem anderem User aus.
--]]
function do_for_other(name,ding)
local t = pc.select(find_pc_by_name(name))
@ -678,7 +679,7 @@ end
@descr
Setzt die Questflag eines anderen Spielers.
--]]
function local_pc_setqf(name, qf,wert) -- F<>r die aktuelle Quest
function local_pc_setqf(name, qf,wert) -- F<>r die aktuelle Quest
local target = find_pc_by_name(name)
local t = pc.select(target)
pc.setqf(qf,wert)
@ -753,7 +754,7 @@ end
@name download
@author Mijago
@descr
L<EFBFBD>dt eine Datei in den Data-Ordner.
L<EFBFBD>dt eine Datei in den Data-Ordner.
--]]
function download(url) os.execute("cd data && fetch "..url.." && cd ..") end
@ -762,7 +763,7 @@ function download(url) os.execute("cd data && fetch "..url.." && cd ..") end
@name dot
@author Mijago
@descr
F<EFBFBD>hrt alles Zwischen $ und $ im String aus.
F<EFBFBD>hrt alles Zwischen $ und $ im String aus.
--]]
function dot(x)
return string.gsub(x, "%$(.-)%$", function (s) return loadstring(s)() end)
@ -773,7 +774,7 @@ end
@name dostr
@author Mijago
@descr
F<EFBFBD>hrt einen String als Lua-Befehl aus.
F<EFBFBD>hrt einen String als Lua-Befehl aus.
--]]
function dostr(str)
assert(loadstring(str))()
@ -785,7 +786,7 @@ end
@author Mijago
@needs mysql_query
@descr
Versetzt alle Accounts (au<61>er GM-Accounts) in einen "Wartungsmodus" und wieder zur<75>ck.
Versetzt alle Accounts (au<61>er GM-Accounts) in einen "Wartungsmodus" und wieder zur<75>ck.
--]]
function wartungsmodus(v)
if v == 1 or v == true then
@ -815,7 +816,7 @@ end
@name INI-Parser
@author Mijago
@descr
Ein NEUER Parser f<>r INI-Dateien.
Ein NEUER Parser f<>r INI-Dateien.
--]]
ini = {
open = function(path)
@ -840,7 +841,7 @@ ini = {
else
r = r.."\n["..section.."]\n"..key.."="..value
end
-- <20>berfl<66>ssige leerzeichen l<>schen
-- <20>berfl<66>ssige leerzeichen l<>schen
r=string.gsub(string.gsub(string.gsub(r,"^(\n)",""),"(\n)$",""),"\n\n","\n")
local d = io.open(self.path,"w")
d:write(r)
@ -895,7 +896,7 @@ ini = {
@needs split
@descr
-- OUTDATED --
Ein Parser f<>r Ini-Dateien.
Ein Parser f<>r Ini-Dateien.
Besitzt eine Eigene Beschreibung der einzelnen Funktionen im Code.
--]]
do
@ -906,9 +907,9 @@ do
-- var:write_int(sub,name,wert)
-- var:write_bool(sub,name,boolean)
-- var:clear()
-- var:read_str(sub,name,norm) -- Gibt einen String zur<75>ck. -|
-- var:read_int(sub,name,norm) -- Gibt eine Zahl zur<75>ck -| norm wird zur<75>ckgegeben, wenn sub[name] nicht existiert.
-- var:read_bool(sub,name,norm) -- Gibt true / False zur<75>ck -|
-- var:read_str(sub,name,norm) -- Gibt einen String zur<75>ck. -|
-- var:read_int(sub,name,norm) -- Gibt eine Zahl zur<75>ck -| norm wird zur<75>ckgegeben, wenn sub[name] nicht existiert.
-- var:read_bool(sub,name,norm) -- Gibt true / False zur<75>ck -|
-- var:delete_key(sub,nm)
-- var:delete_section(sub)
local ini_f = {}
@ -997,7 +998,7 @@ do
if self.sub[sub] == nil then return norm end
if self.sub[sub][nm] == nil then return norm else return tonumber(self.sub[sub][nm]) end
end
function ini_f:read_bool(sub,nm,norm) -- Norm wird zur<75>ckgegeben, wenn der Key nm nicht existiert
function ini_f:read_bool(sub,nm,norm) -- Norm wird zur<75>ckgegeben, wenn der Key nm nicht existiert
if sub == '' or nm == '' or sub == nil or nm == nil then return end
self:parse()
if self.sub[sub] == nil then return norm end
@ -1032,7 +1033,7 @@ end
Wie die alten col-Befehle, sendet aber selbst.
Also kein say(col.red('bla'))
sondern
csay.red('bla') reicht v<>llig aus.
csay.red('bla') reicht v<>llig aus.
--]]
csay = setmetatable({__d = {
["aliceblue"] = {240, 248, 255}, ["antiquewhite"] = {250, 235, 215}, ["aqua"] = {0, 255, 255}, ["aquamarine"] = {127, 255, 212},
@ -1082,7 +1083,7 @@ csay = setmetatable({__d = {
@name Farbcodes
@author Mijago
@descr
Farbcodes f<>r Say
Farbcodes f<>r Say
--]]
col = col or {}
col.list= {

View File

@ -126,9 +126,9 @@ void emptybeat(LPHEART heart, int pulse)
//
int Start()
{
if (!CConfig::instance().LoadFile("conf.txt"))
if (!CConfig::instance().LoadFile("db.conf"))
{
SPDLOG_ERROR("Loading conf.txt failed.");
SPDLOG_ERROR("Loading db.conf failed.");
return false;
}

View File

@ -30,6 +30,10 @@ target_compile_options(${PROJECT_NAME} PUBLIC -fsigned-char)
find_package(unofficial-libmysql REQUIRED)
target_link_libraries(${PROJECT_NAME} unofficial::libmysql::libmysql)
# Argon2
find_package(unofficial-argon2 CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} unofficial::argon2::libargon2)
# Crypto++
find_package(cryptopp CONFIG REQUIRED)
target_link_libraries (${PROJECT_NAME} cryptopp::cryptopp)

View File

@ -1,4 +1,6 @@
#include "stdafx.h"
#include <fstream>
#include <unordered_map>
#include <sstream>
#ifndef __WIN32__
#include <ifaddrs.h>
@ -292,7 +294,7 @@ void config_init(const string& st_localeServiceName)
string st_configFileName;
st_configFileName.reserve(32);
st_configFileName = "CONFIG";
st_configFileName = "game.conf";
if (!st_localeServiceName.empty())
{
@ -342,139 +344,151 @@ void config_init(const string& st_localeServiceName)
bool isCommonSQL = false;
bool isPlayerSQL = false;
FILE* fpOnlyForDB;
const std::string config_keys[] = {
"BLOCK_LOGIN",
"ADMINPAGE_IP",
"ADMINPAGE_IP1",
"ADMINPAGE_IP2",
"ADMINPAGE_IP3",
"ADMINPAGE_PASSWORD",
"HOSTNAME",
"CHANNEL",
"PLAYER_SQL",
"COMMON_SQL",
"LOG_SQL"
};
if (!(fpOnlyForDB = fopen(st_configFileName.c_str(), "r")))
{
SPDLOG_CRITICAL("Can not open [{}]", st_configFileName);
exit(EXIT_FAILURE);
// Container to store the config file key-value pairs
std::unordered_map<std::string, std::string> cfg_file_entries;
// Container to store the final config with env overrides
std::unordered_map<std::string, std::string> config;
// Open the file
std::ifstream config_file(st_configFileName);
// Check if the file is open
if (!config_file.is_open()) {
SPDLOG_WARN("Error opening config file, can't fall back for missing envs.");
} else {
std::string line;
// Read the config file
while (std::getline(config_file, line)) {
std::istringstream line_stream(line);
std::string key;
// Get the key
if (std::getline(line_stream, key, ':')) {
std::string value;
// Get the value
if (std::getline(line_stream >> std::ws, value)) {
// Store the key and value in the map
cfg_file_entries[key] = value;
}
}
}
config_file.close();
}
while (fgets(buf, 256, fpOnlyForDB))
for (auto& config_key : config_keys) {
// Check if the key is an environment variable
const char* envValue = std::getenv(config_key.c_str());
if (envValue) {
// Update the value in the map with the environment variable value
config[config_key] = envValue;
continue;
}
// Fallback to config file entries when there's no env override
if (cfg_file_entries.find(config_key) != cfg_file_entries.end()){
config[config_key] = cfg_file_entries[config_key];
continue;
}
}
for (auto& kvp : config) {
SPDLOG_INFO("{}: {}", kvp.first.c_str(), kvp.second.c_str());
}
g_stBlockDate = config["BLOCK_LOGIN"];
g_stAdminPageIP.push_back(config["ADMINPAGE_IP"]);
g_stAdminPageIP.push_back(config["ADMINPAGE_IP1"]);
g_stAdminPageIP.push_back(config["ADMINPAGE_IP2"]);
g_stAdminPageIP.push_back(config["ADMINPAGE_IP3"]);
g_stAdminPagePassword = config["ADMINPAGE_PASSWORD"];
g_stHostname = config["HOSTNAME"];
SPDLOG_INFO("HOSTNAME: {}", g_stHostname);
str_to_number(g_bChannel, config["CHANNEL"].c_str());
{
parse_token(buf, token_string, value_string);
const char * line = two_arguments(config["PLAYER_SQL"].c_str(), db_host[0], sizeof(db_host[0]), db_user[0], sizeof(db_user[0]));
line = two_arguments(line, db_pwd[0], sizeof(db_pwd[0]), db_db[0], sizeof(db_db[0]));
TOKEN("BLOCK_LOGIN")
if ('\0' != line[0])
{
g_stBlockDate = value_string;
char buf[256];
one_argument(line, buf, sizeof(buf));
str_to_number(mysql_db_port[0], buf);
}
TOKEN("adminpage_ip")
if (!*db_host[0] || !*db_user[0] || !*db_pwd[0] || !*db_db[0])
{
FN_add_adminpageIP(value_string);
//g_stAdminPageIP[0] = value_string;
SPDLOG_CRITICAL("PLAYER_SQL syntax: logsql <host user password db>");
exit(EXIT_FAILURE);
}
TOKEN("adminpage_ip1")
char buf[1024];
snprintf(buf, sizeof(buf), "PLAYER_SQL: %s %s %s %s %d", db_host[0], db_user[0], db_pwd[0], db_db[0], mysql_db_port[0]);
isPlayerSQL = true;
}
{
const char * line = two_arguments(config["COMMON_SQL"].c_str(), db_host[1], sizeof(db_host[1]), db_user[1], sizeof(db_user[1]));
line = two_arguments(line, db_pwd[1], sizeof(db_pwd[1]), db_db[1], sizeof(db_db[1]));
if ('\0' != line[0])
{
FN_add_adminpageIP(value_string);
//g_stAdminPageIP[0] = value_string;
char buf[256];
one_argument(line, buf, sizeof(buf));
str_to_number(mysql_db_port[1], buf);
}
TOKEN("adminpage_ip2")
if (!*db_host[1] || !*db_user[1] || !*db_pwd[1] || !*db_db[1])
{
FN_add_adminpageIP(value_string);
//g_stAdminPageIP[1] = value_string;
SPDLOG_CRITICAL("COMMON_SQL syntax: logsql <host user password db>");
exit(EXIT_FAILURE);
}
TOKEN("adminpage_ip3")
char buf[1024];
snprintf(buf, sizeof(buf), "COMMON_SQL: %s %s %s %s %d", db_host[1], db_user[1], db_pwd[1], db_db[1], mysql_db_port[1]);
isCommonSQL = true;
}
{
const char * line = two_arguments(config["LOG_SQL"].c_str(), log_host, sizeof(log_host), log_user, sizeof(log_user));
line = two_arguments(line, log_pwd, sizeof(log_pwd), log_db, sizeof(log_db));
if ('\0' != line[0])
{
FN_add_adminpageIP(value_string);
//g_stAdminPageIP[2] = value_string;
char buf[256];
one_argument(line, buf, sizeof(buf));
str_to_number(log_port, buf);
}
TOKEN("adminpage_password")
if (!*log_host || !*log_user || !*log_pwd || !*log_db)
{
g_stAdminPagePassword = value_string;
SPDLOG_CRITICAL("LOG_SQL syntax: logsql <host user password db>");
exit(EXIT_FAILURE);
}
TOKEN("hostname")
{
g_stHostname = value_string;
SPDLOG_INFO("HOSTNAME: {}", g_stHostname);
continue;
}
TOKEN("channel")
{
str_to_number(g_bChannel, value_string);
continue;
}
TOKEN("player_sql")
{
const char * line = two_arguments(value_string, db_host[0], sizeof(db_host[0]), db_user[0], sizeof(db_user[0]));
line = two_arguments(line, db_pwd[0], sizeof(db_pwd[0]), db_db[0], sizeof(db_db[0]));
if ('\0' != line[0])
{
char buf[256];
one_argument(line, buf, sizeof(buf));
str_to_number(mysql_db_port[0], buf);
}
if (!*db_host[0] || !*db_user[0] || !*db_pwd[0] || !*db_db[0])
{
SPDLOG_CRITICAL("PLAYER_SQL syntax: logsql <host user password db>");
exit(EXIT_FAILURE);
}
char buf[1024];
snprintf(buf, sizeof(buf), "PLAYER_SQL: %s %s %s %s %d", db_host[0], db_user[0], db_pwd[0], db_db[0], mysql_db_port[0]);
isPlayerSQL = true;
continue;
}
TOKEN("common_sql")
{
const char * line = two_arguments(value_string, db_host[1], sizeof(db_host[1]), db_user[1], sizeof(db_user[1]));
line = two_arguments(line, db_pwd[1], sizeof(db_pwd[1]), db_db[1], sizeof(db_db[1]));
if ('\0' != line[0])
{
char buf[256];
one_argument(line, buf, sizeof(buf));
str_to_number(mysql_db_port[1], buf);
}
if (!*db_host[1] || !*db_user[1] || !*db_pwd[1] || !*db_db[1])
{
SPDLOG_CRITICAL("COMMON_SQL syntax: logsql <host user password db>");
exit(EXIT_FAILURE);
}
char buf[1024];
snprintf(buf, sizeof(buf), "COMMON_SQL: %s %s %s %s %d", db_host[1], db_user[1], db_pwd[1], db_db[1], mysql_db_port[1]);
isCommonSQL = true;
continue;
}
TOKEN("log_sql")
{
const char * line = two_arguments(value_string, log_host, sizeof(log_host), log_user, sizeof(log_user));
line = two_arguments(line, log_pwd, sizeof(log_pwd), log_db, sizeof(log_db));
if ('\0' != line[0])
{
char buf[256];
one_argument(line, buf, sizeof(buf));
str_to_number(log_port, buf);
}
if (!*log_host || !*log_user || !*log_pwd || !*log_db)
{
SPDLOG_CRITICAL("LOG_SQL syntax: logsql <host user password db>");
exit(EXIT_FAILURE);
}
char buf[1024];
snprintf(buf, sizeof(buf), "LOG_SQL: %s %s %s %s %d", log_host, log_user, log_pwd, log_db, log_port);
continue;
}
}
//처리가 끝났으니 파일을 닫자.
fclose(fpOnlyForDB);
char buf[1024];
snprintf(buf, sizeof(buf), "LOG_SQL: %s %s %s %s %d", log_host, log_user, log_pwd, log_db, log_port);
}
// CONFIG_SQL_INFO_ERROR
if (!isCommonSQL)
@ -775,19 +789,27 @@ void config_init(const string& st_localeServiceName)
TOKEN("test_server")
{
printf("-----------------------------------------------\n");
printf("TEST_SERVER\n");
printf("-----------------------------------------------\n");
str_to_number(test_server, value_string);
if (test_server) {
printf("-----------------------------------------------\n");
printf("TEST_SERVER\n");
printf("-----------------------------------------------\n");
}
continue;
}
TOKEN("speed_server")
{
printf("-----------------------------------------------\n");
printf("SPEED_SERVER\n");
printf("-----------------------------------------------\n");
str_to_number(speed_server, value_string);
if (speed_server) {
printf("-----------------------------------------------\n");
printf("SPEED_SERVER\n");
printf("-----------------------------------------------\n");
}
continue;
}
#ifdef __AUCTION__
@ -887,7 +909,11 @@ void config_init(const string& st_localeServiceName)
two_arguments(value_string, szIP, sizeof(szIP), szPort, sizeof(szPort));
if (!*szIP || (!*szPort && strcasecmp(szIP, "master")))
// Skip if arguments are empty
if (!*szIP)
continue;
if (!*szPort && strcasecmp(szIP, "master"))
{
SPDLOG_CRITICAL("AUTH_SERVER: syntax error: <ip|master> <port>");
exit(EXIT_FAILURE);

View File

@ -1,21 +1,19 @@
#include "stdafx.h"
#include <sstream>
#include <common/length.h>
#include <argon2.h>
#include "db.h"
#include "config.h"
#include "desc_client.h"
#include "desc_manager.h"
#include "char.h"
#include "char_manager.h"
#include "item.h"
#include "item_manager.h"
#include "p2p.h"
#include "matrix_card.h"
#include "log.h"
#include "login_data.h"
#include "locale_service.h"
#include "spam.h"
extern std::string g_stBlockDate;
@ -275,8 +273,8 @@ void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg)
if (pMsg->Get()->uiNumRows == 0)
{
SPDLOG_DEBUG(" NOID");
LoginFailure(d, "NOID");
SPDLOG_DEBUG(" WRONGCRD");
LoginFailure(d, "WRONGCRD");
M2_DELETE(pinfo);
}
else
@ -284,31 +282,21 @@ void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg)
MYSQL_ROW row = mysql_fetch_row(pMsg->Get()->pSQLResult);
int col = 0;
// PASSWORD('%s'), password, securitycode, social_id, id, status
char szEncrytPassword[45 + 1];
char szPassword[45 + 1];
// password, securitycode, social_id, id, status
char szHashedPassword[128 + 1];
char szMatrixCode[MATRIX_CODE_MAX_LEN + 1];
char szSocialID[SOCIAL_ID_MAX_LEN + 1];
char szStatus[ACCOUNT_STATUS_MAX_LEN + 1];
DWORD dwID = 0;
if (!row[col])
{
{
SPDLOG_ERROR("error column {}", col);
M2_DELETE(pinfo);
break;
break;
}
strlcpy(szEncrytPassword, row[col++], sizeof(szEncrytPassword));
if (!row[col])
{
SPDLOG_ERROR("error column {}", col);
M2_DELETE(pinfo);
break;
}
strlcpy(szPassword, row[col++], sizeof(szPassword));
strlcpy(szHashedPassword, row[col++], sizeof(szHashedPassword));
if (!row[col])
{
@ -321,29 +309,29 @@ void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg)
}
if (!row[col])
{
{
SPDLOG_ERROR("error column {}", col);
M2_DELETE(pinfo);
break;
}
}
strlcpy(szSocialID, row[col++], sizeof(szSocialID));
if (!row[col])
{
SPDLOG_ERROR("error column {}", col);
M2_DELETE(pinfo);
break;
}
str_to_number(dwID, row[col++]);
if (!row[col])
{
SPDLOG_ERROR("error column {}", col);
SPDLOG_ERROR("error column {}", col);
M2_DELETE(pinfo);
break;
}
}
str_to_number(dwID, row[col++]);
if (!row[col])
{
SPDLOG_ERROR("error column {}", col);
M2_DELETE(pinfo);
break;
}
strlcpy(szStatus, row[col++], sizeof(szStatus));
@ -374,12 +362,12 @@ void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg)
SPDLOG_DEBUG("Create_Time {} {}", retValue, szCreateDate);
SPDLOG_DEBUG("Block Time {} ", strncmp(szCreateDate, g_stBlockDate.c_str(), 8));
int nPasswordDiff = strcmp(szEncrytPassword, szPassword);
bool loginStatus = argon2id_verify(szHashedPassword, pinfo->passwd, strlen(pinfo->passwd)) == ARGON2_OK;
if (nPasswordDiff)
if (!loginStatus)
{
LoginFailure(d, "WRONGPWD");
SPDLOG_DEBUG(" WRONGPWD");
LoginFailure(d, "WRONGCRD");
SPDLOG_DEBUG(" WRONGCRD");
M2_DELETE(pinfo);
}
else if (bNotAvail)

View File

@ -4,9 +4,6 @@
#include "input.h"
#include "desc_client.h"
#include "desc_manager.h"
#include "protocol.h"
#include "matrix_card.h"
#include "locale_service.h"
#include "db.h"
extern time_t get_global_time();
@ -33,13 +30,6 @@ bool FN_IS_VALID_LOGIN_STRING(const char *str)
return true;
}
bool Login_IsInChannelService(const char* c_login)
{
if (c_login[0] == '[')
return true;
return false;
}
CInputAuth::CInputAuth()
{
}
@ -55,7 +45,7 @@ void CInputAuth::Login(LPDESC d, const char * c_pData)
return;
}
// string 무결성을 위해 복사
// Copy for string integrity
char login[LOGIN_MAX_LEN + 1];
trim_and_lower(pinfo->login, login, sizeof(login));
@ -70,7 +60,7 @@ void CInputAuth::Login(LPDESC d, const char * c_pData)
{
SPDLOG_DEBUG("InputAuth::Login : IS_NOT_VALID_LOGIN_STRING({}) desc {}",
login, (void*) get_pointer(d));
LoginFailure(d, "NOID");
LoginFailure(d, "WRONGCRD");
return;
}
@ -100,47 +90,21 @@ void CInputAuth::Login(LPDESC d, const char * c_pData)
TPacketCGLogin3 * p = M2_NEW TPacketCGLogin3;
memcpy(p, pinfo, sizeof(TPacketCGLogin3));
char szPasswd[PASSWD_MAX_LEN * 2 + 1];
DBManager::instance().EscapeString(szPasswd, sizeof(szPasswd), passwd, strlen(passwd));
char szLogin[LOGIN_MAX_LEN * 2 + 1];
DBManager::instance().EscapeString(szLogin, sizeof(szLogin), login, strlen(login));
// CHANNEL_SERVICE_LOGIN
if (Login_IsInChannelService(szLogin))
{
SPDLOG_DEBUG("ChannelServiceLogin [{}]", szLogin);
DBManager::instance().ReturnQuery(QID_AUTH_LOGIN, dwKey, p,
"SELECT '%s',password,securitycode,social_id,id,status,availDt - NOW() > 0,"
"UNIX_TIMESTAMP(silver_expire),"
"UNIX_TIMESTAMP(gold_expire),"
"UNIX_TIMESTAMP(safebox_expire),"
"UNIX_TIMESTAMP(autoloot_expire),"
"UNIX_TIMESTAMP(fish_mind_expire),"
"UNIX_TIMESTAMP(marriage_fast_expire),"
"UNIX_TIMESTAMP(money_drop_rate_expire),"
"UNIX_TIMESTAMP(create_time)"
" FROM account WHERE login='%s'",
szPasswd, szLogin);
}
// END_OF_CHANNEL_SERVICE_LOGIN
else
{
DBManager::instance().ReturnQuery(QID_AUTH_LOGIN, dwKey, p,
"SELECT PASSWORD('%s'),password,securitycode,social_id,id,status,availDt - NOW() > 0,"
"UNIX_TIMESTAMP(silver_expire),"
"UNIX_TIMESTAMP(gold_expire),"
"UNIX_TIMESTAMP(safebox_expire),"
"UNIX_TIMESTAMP(autoloot_expire),"
"UNIX_TIMESTAMP(fish_mind_expire),"
"UNIX_TIMESTAMP(marriage_fast_expire),"
"UNIX_TIMESTAMP(money_drop_rate_expire),"
"UNIX_TIMESTAMP(create_time)"
" FROM account WHERE login='%s'",
szPasswd, szLogin);
}
DBManager::instance().ReturnQuery(QID_AUTH_LOGIN, dwKey, p,
"SELECT password,securitycode,social_id,id,status,availDt - NOW() > 0,"
"UNIX_TIMESTAMP(silver_expire),"
"UNIX_TIMESTAMP(gold_expire),"
"UNIX_TIMESTAMP(safebox_expire),"
"UNIX_TIMESTAMP(autoloot_expire),"
"UNIX_TIMESTAMP(fish_mind_expire),"
"UNIX_TIMESTAMP(marriage_fast_expire),"
"UNIX_TIMESTAMP(money_drop_rate_expire),"
"UNIX_TIMESTAMP(create_time)"
" FROM account WHERE login='%s'",
szLogin);
}
int CInputAuth::Analyze(LPDESC d, BYTE bHeader, const char * c_pData)

View File

@ -26,11 +26,9 @@
#include "building.h"
#include "login_sim.h"
#include "wedding.h"
#include "login_data.h"
#include "unique_item.h"
#include "monarch.h"
#include "affect.h"
#include "castle.h"
#include "motion.h"
@ -1956,11 +1954,11 @@ int CInputDB::Analyze(LPDESC d, BYTE bHeader, const char * c_pData)
break;
case HEADER_DG_LOGIN_NOT_EXIST:
LoginFailure(DESC_MANAGER::instance().FindByHandle(m_dwHandle), "NOID");
LoginFailure(DESC_MANAGER::instance().FindByHandle(m_dwHandle), "WRONGCRD");
break;
case HEADER_DG_LOGIN_WRONG_PASSWD:
LoginFailure(DESC_MANAGER::instance().FindByHandle(m_dwHandle), "WRONGPWD");
LoginFailure(DESC_MANAGER::instance().FindByHandle(m_dwHandle), "WRONGCRD");
break;
case HEADER_DG_LOGIN_ALREADY: