diff --git a/.dockerignore b/.dockerignore index e30c0ba..169e1b6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,4 +15,7 @@ gamefiles/data/quest/pre_qc/ gamefiles/data/quest/qc # Test folder -test/ \ No newline at end of file +test/ + +# Built files +build/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 63d7560..c8f6c5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 . . diff --git a/README.md b/README.md index d6408c2..1aac98a 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Install `vcpkg` according to the [lastest instructions](https://vcpkg.io/en/gett 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: @@ -81,6 +81,7 @@ goodies you wish. Also, a lot of time. - 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) - 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. diff --git a/gamefiles/data/quest/questing.lua b/gamefiles/data/quest/questing.lua index f7f93ba..c7663d8 100644 --- a/gamefiles/data/quest/questing.lua +++ b/gamefiles/data/quest/questing.lua @@ -51,8 +51,8 @@ mysql_query = function(query) 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ät & Fehlerbehandlung + -- START Zusatz: Hanashi-Kompatibilit�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ÜHRT zurückgegeben! +Gibt die M�glichkeit, globale Variablen zu definieren. Es k�nnen auch Funktionen genutzt werden! Diese werden dann AUSGEF�HRT zur�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ück. +Gibt die verbleibende Zeit als String zur�ck. --]] function duration(ipe) local ipe,dat= ipe or 0,'' @@ -183,7 +183,7 @@ end @name is_number @author Mijago @descr -Prüft, ob eine Variable eine Zahl ist. +Pr�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üft, ob eine Variable ein String ist. +Pr�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üft, ob eine Variable eine Tabelle ist. +Pr�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üft, ob eine Variablei in einer Tabelle ist. +Pr�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öglicht die Angabe von min und max auf einmal +Erm�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ür Inputs nur für Zahlen. -Die Zahl ist IMMER positiv. Wenn sie nicht gültig ist, ist sie 0. +F�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öglicht es, längere Inputs zu nutzen. +Erm�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ück und Abbrechen (-1) Buttons. +Weiter, Zur�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ü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�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ügt die Funktion say2 an. +F�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 Ändern des Nutzerpasswortes. +Funktion zum �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ätze für Items der größe X (Höhe). +Checkt auf Freie Inventarpl�tze f�r Items der gr��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ührt einen String als Luabefehle bei einem anderem User aus. +F�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ädt eine Datei in den Data-Ordner. +L�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ührt alles Zwischen $ und $ im String aus. +F�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ührt einen String als Lua-Befehl aus. +F�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ßer GM-Accounts) in einen "Wartungsmodus" und wieder zurück. +Versetzt alle Accounts (au�er GM-Accounts) in einen "Wartungsmodus" und wieder zur�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 - -- überflüssige leerzeichen löschen + -- �berfl�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ück. -| - -- var:read_int(sub,name,norm) -- Gibt eine Zahl zurück -| norm wird zurückgegeben, wenn sub[name] nicht existiert. - -- var:read_bool(sub,name,norm) -- Gibt true / False zurück -| + -- var:read_str(sub,name,norm) -- Gibt einen String zur�ck. -| + -- var:read_int(sub,name,norm) -- Gibt eine Zahl zur�ck -| norm wird zur�ckgegeben, wenn sub[name] nicht existiert. + -- var:read_bool(sub,name,norm) -- Gibt true / False zur�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ückgegeben, wenn der Key nm nicht existiert + function ini_f:read_bool(sub,nm,norm) -- Norm wird zur�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= { diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index a9f7d76..12c85b8 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -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) diff --git a/src/game/src/db.cpp b/src/game/src/db.cpp index 37bda7d..0ec2446 100644 --- a/src/game/src/db.cpp +++ b/src/game/src/db.cpp @@ -284,9 +284,9 @@ 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]; + // '%s', password, securitycode, social_id, id, status + char szPlainPassword[128 + 1]; + 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]; @@ -296,19 +296,19 @@ void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg) { SPDLOG_ERROR("error column {}", col); M2_DELETE(pinfo); - break; + break; } - - strlcpy(szEncrytPassword, row[col++], sizeof(szEncrytPassword)); + + strlcpy(szPlainPassword, row[col++], sizeof(szPlainPassword)); if (!row[col]) { - SPDLOG_ERROR("error column {}", col); + SPDLOG_ERROR("error column {}", col); M2_DELETE(pinfo); - break; - } - - strlcpy(szPassword, row[col++], sizeof(szPassword)); + break; + } + + strlcpy(szHashedPassword, row[col++], sizeof(szHashedPassword)); if (!row[col]) { @@ -321,29 +321,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,9 +374,9 @@ 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 = hash_secure_verify(szHashedPassword, szPlainPassword); - if (nPasswordDiff) + if (!loginStatus) { LoginFailure(d, "WRONGPWD"); SPDLOG_DEBUG(" WRONGPWD"); diff --git a/src/game/src/input_auth.cpp b/src/game/src/input_auth.cpp index a11a1d7..325310f 100644 --- a/src/game/src/input_auth.cpp +++ b/src/game/src/input_auth.cpp @@ -55,7 +55,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)); @@ -106,41 +106,18 @@ void CInputAuth::Login(LPDESC d, const char * c_pData) 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 '%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); } int CInputAuth::Analyze(LPDESC d, BYTE bHeader, const char * c_pData) diff --git a/src/game/src/utils.cpp b/src/game/src/utils.cpp index f12ab0d..556950a 100644 --- a/src/game/src/utils.cpp +++ b/src/game/src/utils.cpp @@ -1,5 +1,11 @@ +#include #include "stdafx.h" +bool hash_secure_verify(const char *hashed_pwd, const char *plain_pwd) +{ + return argon2id_verify(hashed_pwd, plain_pwd, strlen(plain_pwd)) == ARGON2_OK; +} + static int global_time_gap = 0; time_t get_global_time() diff --git a/src/game/src/utils.h b/src/game/src/utils.h index f3baec5..63252f0 100644 --- a/src/game/src/utils.h +++ b/src/game/src/utils.h @@ -9,6 +9,8 @@ #define REMOVE_BIT(var, bit) ((var) &= ~(bit)) #define TOGGLE_BIT(var, bit) ((var) = (var) ^ (bit)) +bool hash_secure_verify(const char *, const char *); + inline float DISTANCE_SQRT(int dx, int dy) { return ::sqrt((float)dx * dx + (float)dy * dy);