diff --git a/config.ini b/config.ini index 732d305..5247d81 100644 --- a/config.ini +++ b/config.ini @@ -17,8 +17,10 @@ acceptallcustomnames=true # should attempts to log into non-existent accounts # automatically create them? autocreateaccounts=true -# support logging in with auth cookies? -useauthcookies=false +# list of supported authentication methods (comma-separated) +# password = allow login type 1 with plaintext passwords +# cookie = allow login type 2 with one-shot auth cookies +authmethods=password # how often should everything be flushed to the database? # the default is 4 minutes dbsaveinterval=240 diff --git a/src/servers/CNLoginServer.cpp b/src/servers/CNLoginServer.cpp index 8beb9ed..9479d71 100644 --- a/src/servers/CNLoginServer.cpp +++ b/src/servers/CNLoginServer.cpp @@ -105,30 +105,20 @@ void loginFail(LoginError errorCode, std::string userLogin, CNSocket* sock) { void CNLoginServer::login(CNSocket* sock, CNPacketData* data) { auto login = (sP_CL2LS_REQ_LOGIN*)data->buf; - bool isCookieAuth = login->iLoginType == 2; std::string userLogin; - std::string userPassword; + std::string userToken; // could be password or auth cookie /* * The std::string -> char* -> std::string maneuver should remove any * trailing garbage after the null terminator. */ - if (isCookieAuth) { - // username encoded in TEGid raw + if (login->iLoginType == (int32_t)LoginType::COOKIE) { userLogin = std::string(AUTOU8(login->szCookie_TEGid).c_str()); - - // N.B. clients that use web login without proper cookies - // send their passwords in the cookie field - userPassword = std::string(AUTOU8(login->szCookie_authid).c_str()); + userToken = std::string(AUTOU8(login->szCookie_authid).c_str()); } else { userLogin = std::string(AUTOU16TOU8(login->szID).c_str()); - userPassword = std::string(AUTOU16TOU8(login->szPassword).c_str()); - } - - if (!settings::USEAUTHCOOKIES) { - // use normal login flow - isCookieAuth = false; + userToken = std::string(AUTOU16TOU8(login->szPassword).c_str()); } // check username regex @@ -145,18 +135,42 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) { return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); } - // check password regex if not cookie auth - if (!isCookieAuth && !CNLoginServer::isPasswordGood(userPassword)) { - // send a custom error message - INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg); - std::string text = "Invalid password\n"; - text += "Password has to be 8 - 32 characters long"; - U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg)); - msg.iDuringTime = 10; - sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); + // we only interpret the token as a cookie if cookie login was used and it's allowed. + // otherwise we interpret it as a password, and this maintains compatibility with + // the auto-login trick used on older clients + bool isCookieAuth = login->iLoginType == (int32_t)LoginType::COOKIE + && CNLoginServer::isLoginTypeAllowed(LoginType::COOKIE); - // we still have to send login fail to prevent softlock - return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); + // password login checks + if (!isCookieAuth) { + // bail if password auth isn't allowed + if (!CNLoginServer::isLoginTypeAllowed(LoginType::PASSWORD)) { + // send a custom error message + INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg); + std::string text = "Password login disabled\n"; + text += "This server has disabled logging in with plaintext passwords.\n"; + text += "Please contact an admin for assistance."; + U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg)); + msg.iDuringTime = 12; + sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); + + // we still have to send login fail to prevent softlock + return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); + } + + // check regex + if (!CNLoginServer::isPasswordGood(userToken)) { + // send a custom error message + INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg); + std::string text = "Invalid password\n"; + text += "Password has to be 8 - 32 characters long"; + U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg)); + msg.iDuringTime = 10; + sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); + + // we still have to send login fail to prevent softlock + return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); + } } Database::Account findUser = {}; @@ -166,18 +180,18 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) { if (findUser.AccountID == 0) { // don't auto-create an account if it's a cookie auth for whatever reason if (settings::AUTOCREATEACCOUNTS && !isCookieAuth) - return newAccount(sock, userLogin, userPassword, login->iClientVerC); + return newAccount(sock, userLogin, userToken, login->iClientVerC); return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock); } if (isCookieAuth) { - const char *cookie = userPassword.c_str(); + const char *cookie = userToken.c_str(); if (!Database::checkCookie(findUser.AccountID, cookie)) return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock); } else { // simple password check - if (!CNLoginServer::isPasswordCorrect(findUser.Password, userPassword)) + if (!CNLoginServer::isPasswordCorrect(findUser.Password, userToken)) return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock); } @@ -665,4 +679,18 @@ bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastn std::regex lastnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)"); return (std::regex_match(Firstname, firstnamecheck) && std::regex_match(Lastname, lastnamecheck)); } + +bool CNLoginServer::isLoginTypeAllowed(LoginType loginType) { + // the config file specifies "comma-separated" but tbh we don't care + switch (loginType) + { + case LoginType::PASSWORD: + return settings::AUTHMETHODS.find("password") != std::string::npos; + case LoginType::COOKIE: + return settings::AUTHMETHODS.find("cookie") != std::string::npos; + default: + break; + } + return false; +} #pragma endregion diff --git a/src/servers/CNLoginServer.hpp b/src/servers/CNLoginServer.hpp index 000154c..4e392cb 100644 --- a/src/servers/CNLoginServer.hpp +++ b/src/servers/CNLoginServer.hpp @@ -23,6 +23,11 @@ enum class LoginError { UPDATED_EUALA_REQUIRED = 9 }; +enum class LoginType { + PASSWORD = 1, + COOKIE = 2 +}; + // WARNING: THERE CAN ONLY BE ONE OF THESE SERVERS AT A TIME!!!!!! TODO: change loginSessions & packet handlers to be non-static class CNLoginServer : public CNServer { private: @@ -44,6 +49,7 @@ private: static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword); static bool isAccountInUse(int accountId); static bool isCharacterNameGood(std::string Firstname, std::string Lastname); + static bool isLoginTypeAllowed(LoginType loginType); static void newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC); // returns true if success static bool exitDuplicate(int accountId); diff --git a/src/settings.cpp b/src/settings.cpp index 85554ea..9815a40 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -13,7 +13,7 @@ bool settings::SANDBOX = true; int settings::LOGINPORT = 23000; bool settings::APPROVEALLNAMES = true; bool settings::AUTOCREATEACCOUNTS = true; -bool settings::USEAUTHCOOKIES = false; +std::string settings::AUTHMETHODS = "password"; int settings::DBSAVEINTERVAL = 240; int settings::SHARDPORT = 23001; @@ -88,7 +88,7 @@ void settings::init() { LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); APPROVEALLNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVEALLNAMES); AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS); - USEAUTHCOOKIES = reader.GetBoolean("login", "useauthcookies", USEAUTHCOOKIES); + AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS); DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL); SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT); SHARDSERVERIP = reader.Get("shard", "ip", SHARDSERVERIP); diff --git a/src/settings.hpp b/src/settings.hpp index a27eada..302c4f6 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -8,7 +8,7 @@ namespace settings { extern int LOGINPORT; extern bool APPROVEALLNAMES; extern bool AUTOCREATEACCOUNTS; - extern bool USEAUTHCOOKIES; + extern std::string AUTHMETHODS; extern int DBSAVEINTERVAL; extern int SHARDPORT; extern std::string SHARDSERVERIP;