diff --git a/conf/login_athena.conf b/conf/login_athena.conf index 51b020de77..719d366ea4 100644 --- a/conf/login_athena.conf +++ b/conf/login_athena.conf @@ -163,6 +163,10 @@ dnsbl_servers: bl.blocklist.de, socks.dnsbl.sorbs.net // Note: see 'doc/md5_hashcheck.txt' for more details. client_hash_check: off +// Enable web authentication token system +// This is required for new clients that get data via an additional API over HTTP +use_web_auth_token: yes + // Client MD5 hashes // The client with the specified hash can be used to log in by players with // a group_id equal to or greater than the given value. diff --git a/sql-files/main.sql b/sql-files/main.sql index 6b50c1a64e..6669526676 100644 --- a/sql-files/main.sql +++ b/sql-files/main.sql @@ -754,8 +754,11 @@ CREATE TABLE IF NOT EXISTS `login` ( `pincode_change` int(11) unsigned NOT NULL DEFAULT '0', `vip_time` int(11) unsigned NOT NULL default '0', `old_group` tinyint(3) NOT NULL default '0', + `web_auth_token` varchar(17) null, + `web_auth_token_enabled` tinyint(2) NOT NULL default '0', PRIMARY KEY (`account_id`), - KEY `name` (`userid`) + KEY `name` (`userid`), + UNIQUE KEY `web_auth_token_key` (`web_auth_token`) ) ENGINE=MyISAM AUTO_INCREMENT=2000000; -- added standard accounts for servers, VERY INSECURE!!! diff --git a/sql-files/upgrades/upgrade_20200625.sql b/sql-files/upgrades/upgrade_20200625.sql new file mode 100644 index 0000000000..8087dfb332 --- /dev/null +++ b/sql-files/upgrades/upgrade_20200625.sql @@ -0,0 +1,5 @@ +ALTER TABLE `login` + ADD COLUMN `web_auth_token` VARCHAR(17) NULL AFTER `old_group`, + ADD COLUMN `web_auth_token_enabled` tinyint(2) NOT NULL default '0' AFTER `web_auth_token`, + ADD UNIQUE KEY `web_auth_token_key` (`web_auth_token`) +; diff --git a/src/char/char.cpp b/src/char/char.cpp index 9d3d29c91b..c12d0f4e56 100644 --- a/src/char/char.cpp +++ b/src/char/char.cpp @@ -1966,7 +1966,7 @@ void char_auth_ok(int fd, struct char_session_data *sd) { { //Character already online. KICK KICK KICK mapif_disconnectplayer(map_server[character->server].fd, character->account_id, character->char_id, 2); if (character->waiting_disconnect == INVALID_TIMER) - character->waiting_disconnect = add_timer(gettick()+20000, char_chardb_waiting_disconnect, character->account_id, 0); + character->waiting_disconnect = add_timer(gettick()+AUTH_TIMEOUT, char_chardb_waiting_disconnect, character->account_id, 0); chclif_send_auth_result(fd,8); return; } diff --git a/src/common/sql.cpp b/src/common/sql.cpp index ae8b155da7..89a2bb7ae5 100644 --- a/src/common/sql.cpp +++ b/src/common/sql.cpp @@ -93,6 +93,14 @@ Sql* Sql_Malloc(void) } +/** + * Retrieves the last error number. + * @param self : sql handle + * @return last error number + */ +unsigned int Sql_GetError( Sql* self ){ + return mysql_errno( &self->handle ); +} static int Sql_P_Keepalive(Sql* self); diff --git a/src/common/sql.hpp b/src/common/sql.hpp index dc695e7b06..fc87a4baf9 100644 --- a/src/common/sql.hpp +++ b/src/common/sql.hpp @@ -68,6 +68,11 @@ struct Sql* Sql_Malloc(void); +/// Retrieves the last error number. +unsigned int Sql_GetError( Sql* self ); + + + /// Establishes a connection. /// /// @return SQL_SUCCESS or SQL_ERROR diff --git a/src/login/account.cpp b/src/login/account.cpp index 7c4a3c9a6d..85efe2894c 100644 --- a/src/login/account.cpp +++ b/src/login/account.cpp @@ -14,6 +14,8 @@ #include "../common/sql.hpp" #include "../common/strlib.hpp" +#include "login.hpp" // login_config + /// global defines /// internal structure @@ -49,6 +51,9 @@ static bool account_db_sql_get_property(AccountDB* self, const char* key, char* static bool account_db_sql_set_property(AccountDB* self, const char* option, const char* value); static bool account_db_sql_create(AccountDB* self, struct mmo_account* acc); static bool account_db_sql_remove(AccountDB* self, const uint32 account_id); +static bool account_db_sql_enable_webtoken( AccountDB* self, const uint32 account_id ); +static bool account_db_sql_disable_webtoken( AccountDB* self, const uint32 account_id ); +static bool account_db_sql_remove_webtokens( AccountDB* self ); static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc); static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const uint32 account_id); static bool account_db_sql_load_str(AccountDB* self, struct mmo_account* acc, const char* userid); @@ -71,6 +76,9 @@ AccountDB* account_db_sql(void) { db->vtable.save = &account_db_sql_save; db->vtable.create = &account_db_sql_create; db->vtable.remove = &account_db_sql_remove; + db->vtable.enable_webtoken = &account_db_sql_enable_webtoken; + db->vtable.disable_webtoken = &account_db_sql_disable_webtoken; + db->vtable.remove_webtokens = &account_db_sql_remove_webtokens; db->vtable.load_num = &account_db_sql_load_num; db->vtable.load_str = &account_db_sql_load_str; db->vtable.iterator = &account_db_sql_iterator; @@ -134,6 +142,8 @@ static bool account_db_sql_init(AccountDB* self) { if( codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, codepage) ) Sql_ShowDebug(sql_handle); + self->remove_webtokens( self ); + return true; } @@ -144,6 +154,10 @@ static bool account_db_sql_init(AccountDB* self) { static void account_db_sql_destroy(AccountDB* self){ AccountDB_SQL* db = (AccountDB_SQL*)self; + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token` = NULL", db->account_db ) ){ + Sql_ShowDebug( db->accounts ); + } + Sql_Free(db->accounts); db->accounts = NULL; aFree(db); @@ -483,7 +497,7 @@ static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account } /** - * Fetch a struct mmo_account from sql. + * Fetch a struct mmo_account from sql, excluding web_auth_token. * @param db: pointer to db * @param acc: pointer of mmo_account to fill * @param account_id: id of user account to take data from @@ -533,6 +547,7 @@ static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, uint32 Sql_GetData(sql_handle, 17, &data, NULL); acc->old_group = atoi(data); #endif Sql_FreeResult(sql_handle); + acc->web_auth_token[0] = '\0'; return true; } @@ -629,6 +644,45 @@ static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, boo } } + if( acc->sex != 'S' && login_config.use_web_auth_token ){ + const int MAX_RETRIES = 20; + int i = 0; + bool success = false; + + // Retry it for a maximum number of retries + do{ + if( SQL_SUCCESS == Sql_Query( sql_handle, "UPDATE `%s` SET `web_auth_token` = LEFT( SHA2( CONCAT( UUID(), RAND() ), 256 ), %d ), `web_auth_token_enabled` = '1' WHERE `account_id` = '%d'", db->account_db, WEB_AUTH_TOKEN_LENGTH - 1, acc->account_id ) ){ + success = true; + break; + } + }while( i < MAX_RETRIES && Sql_GetError( sql_handle ) == 1062 ); + + if( !success ){ + if( i == MAX_RETRIES ){ + ShowError( "Failed to generate a unique web_auth_token with %d retries...\n", i ); + }else{ + Sql_ShowDebug( sql_handle ); + } + + break; + } + + char* data; + size_t len; + + if( SQL_SUCCESS != Sql_Query( sql_handle, "SELECT `web_auth_token` from `%s` WHERE `account_id` = '%d'", db->account_db, acc->account_id ) || + SQL_SUCCESS != Sql_NextRow( sql_handle ) || + SQL_SUCCESS != Sql_GetData( sql_handle, 0, &data, &len ) + ){ + Sql_ShowDebug( sql_handle ); + break; + } + + safestrncpy( (char *)&acc->web_auth_token, data, sizeof( acc->web_auth_token ) ); + + Sql_FreeResult( sql_handle ); + } + // if we got this far, everything was successful result = true; @@ -829,3 +883,36 @@ void mmo_send_global_accreg(AccountDB* self, int fd, uint32 account_id, uint32 c Sql_FreeResult(sql_handle); } + +bool account_db_sql_enable_webtoken( AccountDB* self, const uint32 account_id ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token_enabled` = '1' WHERE `account_id` = '%u'", db->account_db, account_id ) ){ + Sql_ShowDebug( db->accounts ); + return false; + } + + return true; +} + +bool account_db_sql_disable_webtoken( AccountDB* self, const uint32 account_id ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token_enabled` = '0' WHERE `account_id` = '%u'", db->account_db, account_id ) ){ + Sql_ShowDebug( db->accounts ); + return false; + } + + return true; +} + +bool account_db_sql_remove_webtokens( AccountDB* self ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token` = NULL, `web_auth_token_enabled` = '0'", db->account_db ) ){ + Sql_ShowDebug( db->accounts ); + return false; + } + + return true; +} diff --git a/src/login/account.hpp b/src/login/account.hpp index 65ef0b2fa2..2724e2b1f7 100644 --- a/src/login/account.hpp +++ b/src/login/account.hpp @@ -8,6 +8,10 @@ #include "../common/mmo.hpp" // ACCOUNT_REG2_NUM #include "../config/core.hpp" +#ifndef WEB_AUTH_TOKEN_LENGTH +#define WEB_AUTH_TOKEN_LENGTH 16+1 +#endif + typedef struct AccountDB AccountDB; typedef struct AccountDBIterator AccountDBIterator; @@ -32,6 +36,7 @@ struct mmo_account { char birthdate[10+1]; // assigned birth date (format: YYYY-MM-DD) char pincode[PINCODE_LENGTH+1]; // pincode system time_t pincode_change; // (timestamp): last time of pincode change + char web_auth_token[WEB_AUTH_TOKEN_LENGTH]; // web authentication token (randomized on each login) #ifdef VIP_ENABLE int old_group; time_t vip_time; @@ -101,6 +106,15 @@ struct AccountDB { /// @return true if successful bool (*remove)(AccountDB* self, const uint32 account_id); + /// Enables the web auth token for the given account id + bool (*enable_webtoken)(AccountDB* self, const uint32 account_id); + + /// Disables the web auth token for the given account id + bool (*disable_webtoken)(AccountDB* self, const uint32 account_id); + + /// Removes the web auth token for all accounts + bool (*remove_webtokens)(AccountDB* self); + /// Modifies the data of an existing account. /// Uses acc->account_id to identify the account. /// diff --git a/src/login/login.cpp b/src/login/login.cpp index 98672cc809..fc19250da9 100644 --- a/src/login/login.cpp +++ b/src/login/login.cpp @@ -98,6 +98,8 @@ struct online_login_data* login_add_online_user(int char_server, uint32 account_ } } + accounts->enable_webtoken( accounts, account_id ); + return p; } @@ -118,6 +120,8 @@ void login_remove_online_user(uint32 account_id) { delete_timer( p->waiting_disconnect, login_waiting_disconnect_timer ); } + accounts->disable_webtoken( accounts, account_id ); + online_db.erase( account_id ); } @@ -409,9 +413,12 @@ int login_mmo_auth(struct login_session_data* sd, bool isServer) { safestrncpy(acc.last_ip, ip, sizeof(acc.last_ip)); acc.unban_time = 0; acc.logincount++; - accounts->save(accounts, &acc); + if( login_config.use_web_auth_token ){ + safestrncpy( sd->web_auth_token, acc.web_auth_token, WEB_AUTH_TOKEN_LENGTH ); + } + if( sd->sex != 'S' && sd->account_id < START_ACCOUNT_NUM ) ShowWarning("Account %s has account id %d! Account IDs must be over %d to work properly!\n", sd->userid, sd->account_id, START_ACCOUNT_NUM); @@ -637,6 +644,8 @@ bool login_config_read(const char* cfgName, bool normal) { login_config.ip_sync_interval = (unsigned int)1000*60*atoi(w2); //w2 comes in minutes. else if(!strcmpi(w1, "client_hash_check")) login_config.client_hash_check = config_switch(w2); + else if(!strcmpi(w1, "use_web_auth_token")) + login_config.use_web_auth_token = config_switch(w2); else if(!strcmpi(w1, "client_hash")) { int group = 0; char md5[33]; @@ -751,6 +760,7 @@ void login_set_defaults() { login_config.vip_sys.char_increase = MAX_CHAR_VIP; login_config.vip_sys.group = 5; #endif + login_config.use_web_auth_token = true; //other default conf safestrncpy(login_config.loginconf_name, "conf/login_athena.conf", sizeof(login_config.loginconf_name)); diff --git a/src/login/login.hpp b/src/login/login.hpp index f79ac00187..6cac1ee4e1 100644 --- a/src/login/login.hpp +++ b/src/login/login.hpp @@ -45,6 +45,8 @@ struct login_session_data { int has_client_hash; ///client ha sent an hash int fd; ///socket of client + + char web_auth_token[WEB_AUTH_TOKEN_LENGTH]; /// web authentication token }; #define MAX_SERVERS 5 //max number of mapserv that could be attach @@ -109,6 +111,7 @@ struct Login_Config { unsigned int char_increase; /// number of char-slot to increase in VIP state } vip_sys; #endif + bool use_web_auth_token; /// Enable web authentication token system }; extern struct Login_Config login_config; diff --git a/src/login/loginclif.cpp b/src/login/loginclif.cpp index 9d80fb309a..e94eb36e08 100644 --- a/src/login/loginclif.cpp +++ b/src/login/loginclif.cpp @@ -130,7 +130,7 @@ static void logclif_auth_ok(struct login_session_data* sd) { WFIFOW(fd,44) = 0; // unknown WFIFOB(fd,46) = sex_str2num(sd->sex); #if PACKETVER >= 20170315 - memset(WFIFOP(fd,47),0,17); // Unknown + safestrncpy( WFIFOCP( fd, 47 ), sd->web_auth_token, WEB_AUTH_TOKEN_LENGTH ); // web authentication token #endif for( i = 0, n = 0; i < ARRAYLENGTH(ch_server); ++i ) { if( !session_isValid(ch_server[i].fd) )