From a24da6d302d07c5079097ede8aa71da5f80bfc95 Mon Sep 17 00:00:00 2001 From: lighta Date: Tue, 15 Jul 2014 19:49:32 -0400 Subject: [PATCH] -Apply login_refac (from old refac branch) in same way as char_refac, each interface now have his own file. -Remove _sql from some file. Thx to Euphy for helping with documentation. --- conf/login_athena.conf | 6 + conf/subnet_athena.conf | 3 +- src/char/char.c | 38 +- src/config/const.h | 3 + src/login/{account_sql.c => account.c} | 168 +- src/login/account.h | 16 +- src/login/{ipban_sql.c => ipban.c} | 248 +-- src/login/ipban.h | 47 +- src/login/login.c | 1884 ++++------------------ src/login/login.h | 146 +- src/login/loginchrif.c | 967 +++++++++++ src/login/loginchrif.h | 56 + src/login/loginclif.c | 564 +++++++ src/login/loginclif.h | 42 + src/login/logincnslif.c | 176 ++ src/login/logincnslif.h | 58 + src/login/{loginlog_sql.c => loginlog.c} | 160 +- src/login/loginlog.h | 54 +- src/login/sql/CMakeLists.txt | 12 +- vcproj-10/login-server_sql.vcxproj | 12 +- vcproj-12/login-server_sql.vcxproj | 12 +- vcproj-9/login-server_sql.vcproj | 30 +- 22 files changed, 2858 insertions(+), 1844 deletions(-) rename src/login/{account_sql.c => account.c} (86%) rename src/login/{ipban_sql.c => ipban.c} (82%) create mode 100644 src/login/loginchrif.c create mode 100644 src/login/loginchrif.h create mode 100644 src/login/loginclif.c create mode 100644 src/login/loginclif.h create mode 100644 src/login/logincnslif.c create mode 100644 src/login/logincnslif.h rename src/login/{loginlog_sql.c => loginlog.c} (77%) diff --git a/conf/login_athena.conf b/conf/login_athena.conf index 87f128e191..3f6beccac6 100644 --- a/conf/login_athena.conf +++ b/conf/login_athena.conf @@ -180,5 +180,11 @@ client_hash_check: off //client_hash: 10, cb1ea78023d337c38e8ba5124e2338ae //client_hash: 99, disabled +//New registration flood protection [Kevin] +//Number of new registration allowed +allowed_regs: 1 +//Time in second before the counter for the number of registration is reset +time_allowed: 10 + import: conf/inter_athena.conf import: conf/import/login_conf.txt diff --git a/conf/subnet_athena.conf b/conf/subnet_athena.conf index e91fb73768..f278c5f383 100644 --- a/conf/subnet_athena.conf +++ b/conf/subnet_athena.conf @@ -1,6 +1,7 @@ // Subnet support file // Format is: // subnet: net-submask:char_ip:map_ip -// you can add more than one subnet +// you can add more than one subnet (max 16) +// check is if((net-submask & char_ip ) == (net-submask & servip)) => ok subnet: 255.0.0.0:127.0.0.1:127.0.0.1 diff --git a/src/char/char.c b/src/char/char.c index 6bf9017a44..adebfa241a 100644 --- a/src/char/char.c +++ b/src/char/char.c @@ -1968,43 +1968,11 @@ int char_lan_subnetcheck(uint32 ip){ } // Console Command Parser [Wizputer] -int parse_console(const char* buf) -{ - char type[64]; - char command[64]; - int n=0; - - if( ( n = sscanf(buf, "%63[^:]:%63[^\n]", type, command) ) < 2 ){ - if((n = sscanf(buf, "%63[^\n]", type))<1) return -1; //nothing to do no arg - } - if( n != 2 ){ //end string - ShowNotice("Type: '%s'\n",type); - command[0] = '\0'; - } - else - ShowNotice("Type of command: '%s' || Command: '%s'\n",type,command); - - if( n == 2 && strcmpi("server", type) == 0 ){ - if( strcmpi("shutdown", command) == 0 || strcmpi("exit", command) == 0 || strcmpi("quit", command) == 0 ){ - runflag = CHARSERVER_ST_SHUTDOWN; - } - else if( strcmpi("alive", command) == 0 || strcmpi("status", command) == 0 ) - ShowInfo(CL_CYAN"Console: "CL_BOLD"I'm Alive."CL_RESET"\n"); - } - else if( strcmpi("ers_report", type) == 0 ){ - ers_report(); - } - else if( strcmpi("help", type) == 0 ){ - ShowInfo("Available commands:\n"); - ShowInfo("\t server:shutdown => Stops the server.\n"); - ShowInfo("\t server:alive => Checks if the server is running.\n"); - ShowInfo("\t ers_report => Displays database usage.\n"); - } - - return 0; +//FIXME to be remove (moved to cnslif / will be done once map/char/login, all have their cnslif interface ready) +int parse_console(const char* buf){ + return cnslif_parse(buf); } - //------------------------------------------------ //Pincode system //------------------------------------------------ diff --git a/src/config/const.h b/src/config/const.h index 29765c9483..6fdaa56c70 100644 --- a/src/config/const.h +++ b/src/config/const.h @@ -89,6 +89,9 @@ /* Feb 1st 2012 */ #if PACKETVER >= 20120201 #define NEW_CARTS + #ifndef ENABLE_SC_SAVING + #warning "Cart won't be able to be saved for relog" + #endif #define MAX_CARTS 9 #else #define MAX_CARTS 5 diff --git a/src/login/account_sql.c b/src/login/account.c similarity index 86% rename from src/login/account_sql.c rename to src/login/account.c index 8ede02c38c..73e4fdc750 100644 --- a/src/login/account_sql.c +++ b/src/login/account.c @@ -1,5 +1,11 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder +/** + * @file account.c + * Module purpose is to save, load, and update changes into the account table or file. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams < r15k + * @author rAthena Dev Team + */ #include "../common/malloc.h" #include "../common/mmo.h" @@ -16,8 +22,7 @@ #define ACCOUNT_SQL_DB_VERSION 20110114 /// internal structure -typedef struct AccountDB_SQL -{ +typedef struct AccountDB_SQL { AccountDB vtable; // public interface Sql* accounts; // SQL accounts storage @@ -44,10 +49,8 @@ typedef struct AccountDB_SQL } AccountDB_SQL; /// internal structure -typedef struct AccountDBIterator_SQL -{ +typedef struct AccountDBIterator_SQL { AccountDBIterator vtable; // public interface - AccountDB_SQL* db; int last_account_id; } AccountDBIterator_SQL; @@ -70,8 +73,7 @@ static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, int acc static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new); /// public constructor -AccountDB* account_db_sql(void) -{ +AccountDB* account_db_sql(void) { AccountDB_SQL* db = (AccountDB_SQL*)aCalloc(1, sizeof(AccountDB_SQL)); // set up the vtable @@ -114,9 +116,11 @@ AccountDB* account_db_sql(void) /* ------------------------------------------------------------------------- */ -/// establishes database connection -static bool account_db_sql_init(AccountDB* self) -{ +/** + * Establish the database connection. + * @param self: pointer to db + */ +static bool account_db_sql_init(AccountDB* self) { AccountDB_SQL* db = (AccountDB_SQL*)self; Sql* sql_handle; const char* username; @@ -162,9 +166,11 @@ static bool account_db_sql_init(AccountDB* self) return true; } -/// disconnects from database -static void account_db_sql_destroy(AccountDB* self) -{ +/** + * Destroy the database and close the connection to it. + * @param self: pointer to db + */ +static void account_db_sql_destroy(AccountDB* self){ AccountDB_SQL* db = (AccountDB_SQL*)self; Sql_Free(db->accounts); @@ -172,7 +178,15 @@ static void account_db_sql_destroy(AccountDB* self) aFree(db); } -/// Gets a property from this database. +/** + * Get configuration information into buf. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param buf: value set of the keyword + * @param buflen: size of buffer to avoid out of bound + * @return true if successful, false if something has failed + */ static bool account_db_sql_get_property(AccountDB* self, const char* key, char* buf, size_t buflen) { AccountDB_SQL* db = (AccountDB_SQL*)self; @@ -259,9 +273,15 @@ static bool account_db_sql_get_property(AccountDB* self, const char* key, char* return false;// not found } -/// if the option is supported, adjusts the internal state -static bool account_db_sql_set_property(AccountDB* self, const char* key, const char* value) -{ +/** + * Read and set configuration. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if something has failed + */ +static bool account_db_sql_set_property(AccountDB* self, const char* key, const char* value) { AccountDB_SQL* db = (AccountDB_SQL*)self; const char* signature; @@ -330,11 +350,15 @@ static bool account_db_sql_set_property(AccountDB* self, const char* key, const return false;// not found } -/// create a new account entry -/// If acc->account_id is -1, the account id will be auto-generated, -/// and its value will be written to acc->account_id if everything succeeds. -static bool account_db_sql_create(AccountDB* self, struct mmo_account* acc) -{ +/** + * Create a new account entry. + * If acc->account_id is -1, the account id will be auto-generated, + * and its value will be written to acc->account_id if everything succeeds. + * @param self: pointer to db + * @param acc: pointer of mmo_account to save + * @return true if successful, false if something has failed + */ +static bool account_db_sql_create(AccountDB* self, struct mmo_account* acc) { AccountDB_SQL* db = (AccountDB_SQL*)self; Sql* sql_handle = db->accounts; @@ -383,9 +407,13 @@ static bool account_db_sql_create(AccountDB* self, struct mmo_account* acc) return mmo_auth_tosql(db, acc, true); } -/// delete an existing account entry + its regs -static bool account_db_sql_remove(AccountDB* self, const int account_id) -{ +/** + * Delete an existing account entry and its regs. + * @param self: pointer to db + * @param account_id: id of user account + * @return true if successful, false if something has failed + */ +static bool account_db_sql_remove(AccountDB* self, const int account_id) { AccountDB_SQL* db = (AccountDB_SQL*)self; Sql* sql_handle = db->accounts; bool result = false; @@ -402,23 +430,40 @@ static bool account_db_sql_remove(AccountDB* self, const int account_id) return result; } -/// update an existing account with the provided new data (both account and regs) -static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc) -{ +/** + * Update an existing account with the new data provided (both account and regs). + * @param self: pointer to db + * @param acc: pointer of mmo_account to save + * @return true if successful, false if something has failed + */ +static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc) { AccountDB_SQL* db = (AccountDB_SQL*)self; return mmo_auth_tosql(db, acc, false); } -/// retrieve data from db and store it in the provided data structure -static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const int account_id) -{ +/** + * Retrieve data from db and store it in the provided data structure. + * Filled data structure is done by delegation to mmo_auth_fromsql. + * @param self: pointer to db + * @param acc: pointer of mmo_account to fill + * @param account_id: id of user account + * @return true if successful, false if something has failed + */ +static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const int account_id) { AccountDB_SQL* db = (AccountDB_SQL*)self; return mmo_auth_fromsql(db, acc, account_id); } -/// retrieve data from db and store it in the provided data structure -static bool account_db_sql_load_str(AccountDB* self, struct mmo_account* acc, const char* userid) -{ +/** + * Retrieve data from db and store it in the provided data structure. + * Doesn't actually retrieve data yet: escapes and checks userid, then transforms it to accid for fetching. + * Filled data structure is done by delegation to account_db_sql_load_num. + * @param self: pointer to db + * @param acc: pointer of mmo_account to fill + * @param userid: name of user account + * @return true if successful, false if something has failed + */ +static bool account_db_sql_load_str(AccountDB* self, struct mmo_account* acc, const char* userid) { AccountDB_SQL* db = (AccountDB_SQL*)self; Sql* sql_handle = db->accounts; char esc_userid[2*NAME_LENGTH+1]; @@ -454,10 +499,12 @@ static bool account_db_sql_load_str(AccountDB* self, struct mmo_account* acc, co return account_db_sql_load_num(self, acc, account_id); } - -/// Returns a new forward iterator. -static AccountDBIterator* account_db_sql_iterator(AccountDB* self) -{ +/** + * Create a new forward iterator. + * @param self: pointer to db iterator + * @return a new db iterator + */ +static AccountDBIterator* account_db_sql_iterator(AccountDB* self) { AccountDB_SQL* db = (AccountDB_SQL*)self; AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)aCalloc(1, sizeof(AccountDBIterator_SQL)); @@ -472,18 +519,22 @@ static AccountDBIterator* account_db_sql_iterator(AccountDB* self) return &iter->vtable; } - -/// Destroys this iterator, releasing all allocated memory (including itself). -static void account_db_sql_iter_destroy(AccountDBIterator* self) -{ +/** + * Destroys this iterator, releasing all allocated memory (including itself). + * @param self: pointer to db iterator + */ +static void account_db_sql_iter_destroy(AccountDBIterator* self) { AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self; aFree(iter); } - -/// Fetches the next account in the database. -static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account* acc) -{ +/** + * Fetches the next account in the database. + * @param self: pointer to db iterator + * @param acc: pointer of mmo_account to fill + * @return true if next account found and filled, false if something has failed + */ +static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account* acc) { AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self; AccountDB_SQL* db = (AccountDB_SQL*)iter->db; Sql* sql_handle = db->accounts; @@ -514,9 +565,14 @@ static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account return false; } - -static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, int account_id) -{ +/** + * Fetch a struct mmo_account from sql. + * @param db: pointer to db + * @param acc: pointer of mmo_account to fill + * @param account_id: id of user account to take data from + * @return true if successful, false if something has failed + */ +static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, int account_id) { Sql* sql_handle = db->accounts; char* data; int i = 0; @@ -587,8 +643,14 @@ static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, int acc return true; } -static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new) -{ +/** + * Save a struct mmo_account in sql. + * @param db: pointer to db + * @param acc: pointer of mmo_account to save + * @param is_new: if it's a new entry or should we update + * @return true if successful, false if something has failed + */ +static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new) { Sql* sql_handle = db->accounts; SqlStmt* stmt = SqlStmt_Malloc(sql_handle); bool result = false; diff --git a/src/login/account.h b/src/login/account.h index 1d56719fe2..fc2ad02e16 100644 --- a/src/login/account.h +++ b/src/login/account.h @@ -1,5 +1,11 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder +/** + * @file account.h + * Module purpose is to save, load, and update changes into the account table or file. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams < r15k + * @author rAthena Dev Team + */ #ifndef __ACCOUNT_H_INCLUDED__ #define __ACCOUNT_H_INCLUDED__ @@ -61,8 +67,7 @@ struct mmo_account { }; -struct AccountDBIterator -{ +struct AccountDBIterator { /// Destroys this iterator, releasing all allocated memory (including itself). /// /// @param self Iterator @@ -77,8 +82,7 @@ struct AccountDBIterator }; -struct AccountDB -{ +struct AccountDB { /// Initializes this database, making it ready for use. /// Call this after setting the properties. /// diff --git a/src/login/ipban_sql.c b/src/login/ipban.c similarity index 82% rename from src/login/ipban_sql.c rename to src/login/ipban.c index c75a1f9561..b49a6434b5 100644 --- a/src/login/ipban_sql.c +++ b/src/login/ipban.c @@ -1,5 +1,12 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder +/** + * @file ipban.c + * Module purpose is to read configuration for login-server and handle accounts, + * and also to synchronize all login interfaces: loginchrif, loginclif, logincnslif. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams < r15k + * @author rAthena Dev Team + */ #include "../common/cbasetypes.h" #include "../common/db.h" @@ -35,82 +42,90 @@ static Sql* sql_handle = NULL; static int cleanup_timer_id = INVALID_TIMER; static bool ipban_inited = false; +//early declaration int ipban_cleanup(int tid, unsigned int tick, int id, intptr_t data); - -// initialize -void ipban_init(void) -{ - const char* username; - const char* password; - const char* hostname; - uint16 port; - const char* database; - const char* codepage; - - ipban_inited = true; +/** + * Check if ip is in the active bans list. + * @param ip: ipv4 ip to check if ban + * @return true if found or error, false if not in list + */ +bool ipban_check(uint32 ip) { + uint8* p = (uint8*)&ip; + char* data = NULL; + int matches; if( !login_config.ipban ) - return;// ipban disabled + return false;// ipban disabled - if( ipban_db_hostname[0] != '\0' ) - {// local settings - username = ipban_db_username; - password = ipban_db_password; - hostname = ipban_db_hostname; - port = ipban_db_port; - database = ipban_db_database; - codepage = ipban_codepage; - } - else - {// global settings - username = global_db_username; - password = global_db_password; - hostname = global_db_hostname; - port = global_db_port; - database = global_db_database; - codepage = global_codepage; - } - - // establish connections - sql_handle = Sql_Malloc(); - if( SQL_ERROR == Sql_Connect(sql_handle, username, password, hostname, port, database) ) + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT count(*) FROM `%s` WHERE `rtime` > NOW() AND (`list` = '%u.*.*.*' OR `list` = '%u.%u.*.*' OR `list` = '%u.%u.%u.*' OR `list` = '%u.%u.%u.%u')", + ipban_table, p[3], p[3], p[2], p[3], p[2], p[1], p[3], p[2], p[1], p[0]) ) { Sql_ShowDebug(sql_handle); - Sql_Free(sql_handle); - exit(EXIT_FAILURE); + // close connection because we can't verify their connectivity. + return true; } - if( codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, codepage) ) - Sql_ShowDebug(sql_handle); - if( login_config.ipban_cleanup_interval > 0 ) - { // set up periodic cleanup of connection history and active bans - add_timer_func_list(ipban_cleanup, "ipban_cleanup"); - cleanup_timer_id = add_timer_interval(gettick()+10, ipban_cleanup, 0, 0, login_config.ipban_cleanup_interval*1000); - } else // make sure it gets cleaned up on login-server start regardless of interval-based cleanups - ipban_cleanup(0,0,0,0); + if( SQL_ERROR == Sql_NextRow(sql_handle) ) + return true;// Shouldn't happen, but just in case... + + Sql_GetData(sql_handle, 0, &data, NULL); + matches = atoi(data); + Sql_FreeResult(sql_handle); + + return( matches > 0 ); } -// finalize -void ipban_final(void) -{ +/** + * Log a failed attempt. + * Also bans the user if too many failed attempts are made. + * @param ip: ipv4 ip to record the failure + */ +void ipban_log(uint32 ip) { + unsigned long failures; + if( !login_config.ipban ) return;// ipban disabled - if( login_config.ipban_cleanup_interval > 0 ) - // release data - delete_timer(cleanup_timer_id, ipban_cleanup); - - ipban_cleanup(0,0,0,0); // always clean up on login-server stop + failures = loginlog_failedattempts(ip, login_config.dynamic_pass_failure_ban_interval);// how many times failed account? in one ip. - // close connections - Sql_Free(sql_handle); - sql_handle = NULL; + // if over the limit, add a temporary ban entry + if( failures >= login_config.dynamic_pass_failure_ban_limit ) + { + uint8* p = (uint8*)&ip; + if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`list`,`btime`,`rtime`,`reason`) VALUES ('%u.%u.%u.*', NOW() , NOW() + INTERVAL %d MINUTE ,'Password error ban')", + ipban_table, p[3], p[2], p[1], login_config.dynamic_pass_failure_ban_duration) ) + Sql_ShowDebug(sql_handle); + } } -// load configuration options -bool ipban_config_read(const char* key, const char* value) -{ +/** + * Timered function to remove expired bans. + * Request all characters to update their registered ip and transmit their new ip. + * Performed each ip_sync_interval. + * @param tid: timer id + * @param tick: tick of execution + * @param id: unused + * @param data: unused + * @return 0 + */ +int ipban_cleanup(int tid, unsigned int tick, int id, intptr_t data) { + if( !login_config.ipban ) + return 0;// ipban disabled + + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `ipbanlist` WHERE `rtime` <= NOW()") ) + Sql_ShowDebug(sql_handle); + + return 0; +} + +/** + * Read configuration options. + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if config not complete or server already running + */ +bool ipban_config_read(const char* key, const char* value) { const char* signature; if( ipban_inited ) @@ -197,62 +212,79 @@ bool ipban_config_read(const char* key, const char* value) return false;// not found } -// check ip against active bans list -bool ipban_check(uint32 ip) -{ - uint8* p = (uint8*)&ip; - char* data = NULL; - int matches; - if( !login_config.ipban ) - return false;// ipban disabled +/// Constructor destructor - if( SQL_ERROR == Sql_Query(sql_handle, "SELECT count(*) FROM `%s` WHERE `rtime` > NOW() AND (`list` = '%u.*.*.*' OR `list` = '%u.%u.*.*' OR `list` = '%u.%u.%u.*' OR `list` = '%u.%u.%u.%u')", - ipban_table, p[3], p[3], p[2], p[3], p[2], p[1], p[3], p[2], p[1], p[0]) ) - { - Sql_ShowDebug(sql_handle); - // close connection because we can't verify their connectivity. - return true; - } +/** + * Initialize the module. + * Launched at login-serv start, create db or other long scope variable here. + */ +void ipban_init(void) { + const char* username; + const char* password; + const char* hostname; + uint16 port; + const char* database; + const char* codepage; - if( SQL_ERROR == Sql_NextRow(sql_handle) ) - return true;// Shouldn't happen, but just in case... - - Sql_GetData(sql_handle, 0, &data, NULL); - matches = atoi(data); - Sql_FreeResult(sql_handle); - - return( matches > 0 ); -} - -// log failed attempt -void ipban_log(uint32 ip) -{ - unsigned long failures; + ipban_inited = true; if( !login_config.ipban ) return;// ipban disabled - failures = loginlog_failedattempts(ip, login_config.dynamic_pass_failure_ban_interval);// how many times failed account? in one ip. - - // if over the limit, add a temporary ban entry - if( failures >= login_config.dynamic_pass_failure_ban_limit ) - { - uint8* p = (uint8*)&ip; - if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`list`,`btime`,`rtime`,`reason`) VALUES ('%u.%u.%u.*', NOW() , NOW() + INTERVAL %d MINUTE ,'Password error ban')", - ipban_table, p[3], p[2], p[1], login_config.dynamic_pass_failure_ban_duration) ) - Sql_ShowDebug(sql_handle); + if( ipban_db_hostname[0] != '\0' ) + {// local settings + username = ipban_db_username; + password = ipban_db_password; + hostname = ipban_db_hostname; + port = ipban_db_port; + database = ipban_db_database; + codepage = ipban_codepage; + } + else + {// global settings + username = global_db_username; + password = global_db_password; + hostname = global_db_hostname; + port = global_db_port; + database = global_db_database; + codepage = global_codepage; } -} -// remove expired bans -int ipban_cleanup(int tid, unsigned int tick, int id, intptr_t data) -{ - if( !login_config.ipban ) - return 0;// ipban disabled - - if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `ipbanlist` WHERE `rtime` <= NOW()") ) + // establish connections + sql_handle = Sql_Malloc(); + if( SQL_ERROR == Sql_Connect(sql_handle, username, password, hostname, port, database) ) + { + Sql_ShowDebug(sql_handle); + Sql_Free(sql_handle); + exit(EXIT_FAILURE); + } + if( codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, codepage) ) Sql_ShowDebug(sql_handle); - return 0; + if( login_config.ipban_cleanup_interval > 0 ) + { // set up periodic cleanup of connection history and active bans + add_timer_func_list(ipban_cleanup, "ipban_cleanup"); + cleanup_timer_id = add_timer_interval(gettick()+10, ipban_cleanup, 0, 0, login_config.ipban_cleanup_interval*1000); + } else // make sure it gets cleaned up on login-server start regardless of interval-based cleanups + ipban_cleanup(0,0,0,0); +} + +/** + * Destroy the module. + * Launched at login-serv end, cleanup db connection or other thing here. + */ +void ipban_final(void) { + if( !login_config.ipban ) + return;// ipban disabled + + if( login_config.ipban_cleanup_interval > 0 ) + // release data + delete_timer(cleanup_timer_id, ipban_cleanup); + + ipban_cleanup(0,0,0,0); // always clean up on login-server stop + + // close connections + Sql_Free(sql_handle); + sql_handle = NULL; } diff --git a/src/login/ipban.h b/src/login/ipban.h index b2a1a7d9e7..0448ee3131 100644 --- a/src/login/ipban.h +++ b/src/login/ipban.h @@ -1,25 +1,50 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder +/** + * @file ipban.h + * Module purpose is to read configuration for login-server and handle accounts, + * and also to synchronize all login interfaces: loginchrif, loginclif, logincnslif. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams < r15k + * @author rAthena Dev Team + */ #ifndef __IPBAN_H_INCLUDED__ #define __IPBAN_H_INCLUDED__ #include "../common/cbasetypes.h" -// initialize -void ipban_init(void); - -// finalize -void ipban_final(void); - -// check ip against ban list +/** + * Check if ip is in the active bans list. + * @param ip: ipv4 ip to check if ban + * @return true if found or error, false if not in list + */ bool ipban_check(uint32 ip); -// increases failure count for the specified IP +/** + * Log a failed attempt. + * Also bans the user if too many failed attempts are made. + * @param ip: ipv4 ip to record the failure + */ void ipban_log(uint32 ip); -// parses configuration option +/** + * Read configuration options. + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if config not complete or server already running + */ bool ipban_config_read(const char* key, const char* value); +/** + * Initialize the module. + * Launched at login-serv start, create db or other long scope variable here. + */ +void ipban_init(void); + +/** + * Destroy the module. + * Launched at login-serv end, cleanup db connection or other thing here. + */ +void ipban_final(void); #endif // __IPBAN_H_INCLUDED__ diff --git a/src/login/login.c b/src/login/login.c index 658c1b9cc6..cf45eeb7d3 100644 --- a/src/login/login.c +++ b/src/login/login.c @@ -1,5 +1,12 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder +/** + * @file login.c + * Module purpose is to read configuration for login-server and handle accounts, + * and also to synchronize all login interfaces: loginchrif, loginclif, logincnslif. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams < r15k + * @author rAthena Dev Team + */ #include "../common/core.h" #include "../common/db.h" @@ -7,7 +14,7 @@ #include "../common/md5calc.h" #include "../common/random.h" #include "../common/showmsg.h" -#include "../common/socket.h" +#include "../common/socket.h" //ip2str #include "../common/strlib.h" #include "../common/timer.h" #include "../common/msg_conf.h" @@ -20,6 +27,9 @@ #include "ipban.h" #include "login.h" #include "loginlog.h" +#include "loginclif.h" +#include "loginchrif.h" +#include "logincnslif.h" #include #include @@ -27,10 +37,12 @@ #define LOGIN_MAX_MSG 30 /// Max number predefined in msg_conf static char* msg_table[LOGIN_MAX_MSG]; /// Login Server messages_conf -struct Login_Config login_config; /// Configuration of login-serv -int login_fd; // login server socket -struct mmo_char_server ch_server[MAX_SERVERS]; // char server data +//definition of exported var declared in .h +struct mmo_char_server ch_server[MAX_SERVERS]; /// char server data +struct Login_Config login_config; /// Configuration of login-serv +DBMap* online_db; +DBMap* auth_db; // Account engines available static struct{ @@ -58,57 +70,63 @@ static struct{ }; // account database AccountDB* accounts = NULL; - -//Account registration flood protection [Kevin] -int allowed_regs = 1; -int time_allowed = 10; //in seconds - // Advanced subnet check [LuzZza] struct s_subnet { uint32 mask; uint32 char_ip; uint32 map_ip; } subnet[16]; +int subnet_count = 0; //number of subnet config -int subnet_count = 0; +int login_fd; // login server file descriptor socket -int mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip); +//early declaration +bool login_check_password(const char* md5key, int passwdenc, const char* passwd, const char* refpass); -//----------------------------------------------------- -// Auth database -//----------------------------------------------------- -#define AUTH_TIMEOUT 30000 - -struct auth_node { - int account_id; - uint32 login_id1; - uint32 login_id2; - uint32 ip; - char sex; - uint32 version; - uint8 clienttype; -}; -static DBMap* auth_db; // int account_id -> struct auth_node* - - -//----------------------------------------------------- -// Online User Database [Wizputer] -//----------------------------------------------------- -struct online_login_data { - int account_id; - int waiting_disconnect; - int char_server; -}; -static DBMap* online_db; // int account_id -> struct online_login_data* - -static int waiting_disconnect_timer(int tid, unsigned int tick, int id, intptr_t data); +///Accessors +AccountDB* login_get_accounts_db(void){ + return accounts; +} /** - * @see DBCreateData - * Create an online_login_data struct and add it into online db - * + * Get the engine selected in the config settings. + * Updates the config setting with the selected engine if 'auto'. + * @param key: Key of the database entry + * @param ap: args + * @return : Data identified by the key to be put in the database */ -static DBData create_online_user(DBKey key, va_list args){ +static AccountDB* get_account_engine(void) { + int i; + bool get_first = (strcmp(login_config.account_engine,"auto") == 0); + + for( i = 0; account_engines[i].constructor; ++i ) { + char name[sizeof(login_config.account_engine)]; + AccountDB* db = account_engines[i].db; + if( db && db->get_property(db, "engine.name", name, sizeof(name)) && + (get_first || strcmp(name, login_config.account_engine) == 0) ) + { + if( get_first ) + safestrncpy(login_config.account_engine, name, sizeof(login_config.account_engine)); + return db; + } + } + return NULL; +} + +// Console Command Parser [Wizputer] +//FIXME to be remove (moved to cnslif / will be done once map/char/login, all have their cnslif interface ready) +int parse_console(const char* buf){ + return cnslif_parse(buf); +} + +/** + * Sub function to create an online_login_data and save it to db. + * @param key: Key of the database entry + * @param ap: args + * @return : Data identified by the key to be put in the database + * @see DBCreateData + */ +DBData login_create_online_user(DBKey key, va_list args) { struct online_login_data* p; CREATE(p, struct online_login_data, 1); p->account_id = key.i; @@ -124,14 +142,12 @@ static DBData create_online_user(DBKey key, va_list args){ * @param account_id : aid connected * @return the new online_login_data for that user */ -struct online_login_data* add_online_user(int char_server, int account_id) -{ +struct online_login_data* login_add_online_user(int char_server, int account_id){ struct online_login_data* p; - p = idb_ensure(online_db, account_id, create_online_user); + p = idb_ensure(online_db, account_id, login_create_online_user); p->char_server = char_server; - if( p->waiting_disconnect != INVALID_TIMER ) - { - delete_timer(p->waiting_disconnect, waiting_disconnect_timer); + if( p->waiting_disconnect != INVALID_TIMER ) { + delete_timer(p->waiting_disconnect, login_waiting_disconnect_timer); p->waiting_disconnect = INVALID_TIMER; } return p; @@ -140,53 +156,56 @@ struct online_login_data* add_online_user(int char_server, int account_id) /** * Received info from char serv that the account_id is now offline * remove the user from online_db + * Checking if user was already scheduled for deletion, and remove that timer if found. * @param account_id : aid to remove from db */ -void remove_online_user(int account_id) -{ +void login_remove_online_user(int account_id) { struct online_login_data* p; p = (struct online_login_data*)idb_get(online_db, account_id); if( p == NULL ) return; if( p->waiting_disconnect != INVALID_TIMER ) - delete_timer(p->waiting_disconnect, waiting_disconnect_timer); + delete_timer(p->waiting_disconnect, login_waiting_disconnect_timer); idb_remove(online_db, account_id); } /** - * Timered fonction to check if the user still connected - * @param tid - * @param tick - * @param id - * @param data - * @return + * Timered function to disconnect a user from login. + * This is done either after auth_ok or kicked by char-server. + * Removing user from auth_db and online_db. + * Delay is AUTH_TIMEOUT by default. + * @param tid: timer id + * @param tick: tick of execution + * @param id: user account id + * @param data: unused + * @return :0 */ -static int waiting_disconnect_timer(int tid, unsigned int tick, int id, intptr_t data) -{ +int login_waiting_disconnect_timer(int tid, unsigned int tick, int id, intptr_t data) { struct online_login_data* p = (struct online_login_data*)idb_get(online_db, id); - if( p != NULL && p->waiting_disconnect == tid && p->account_id == id ) - { + if( p != NULL && p->waiting_disconnect == tid && p->account_id == id ){ p->waiting_disconnect = INVALID_TIMER; - remove_online_user(id); + login_remove_online_user(id); idb_remove(auth_db, id); } return 0; } /** + * Sub function to apply on online_db. + * Mark a character as offline. + * @param data: 1 entry in the db + * @param ap: args + * @return : Value to be added up by the function that is applying this * @see DBApply */ -static int online_db_setoffline(DBKey key, DBData *data, va_list ap) -{ +int login_online_db_setoffline(DBKey key, DBData *data, va_list ap) { struct online_login_data* p = db_data2ptr(data); int server = va_arg(ap, int); - if( server == -1 ) - { + if( server == -1 ) { p->char_server = -1; - if( p->waiting_disconnect != INVALID_TIMER ) - { - delete_timer(p->waiting_disconnect, waiting_disconnect_timer); + if( p->waiting_disconnect != INVALID_TIMER ) { + delete_timer(p->waiting_disconnect, login_waiting_disconnect_timer); p->waiting_disconnect = INVALID_TIMER; } } @@ -196,884 +215,47 @@ static int online_db_setoffline(DBKey key, DBData *data, va_list ap) } /** + * Sub function of login_online_data_cleanup. + * Checking if all users in db are still connected to a char-server, and remove them if they aren't. + * @param data: 1 entry in the db + * @param ap: args + * @return: Value to be added up by the function that is applying this * @see DBApply */ -static int online_data_cleanup_sub(DBKey key, DBData *data, va_list ap) -{ +static int login_online_data_cleanup_sub(DBKey key, DBData *data, va_list ap) { struct online_login_data *character= db_data2ptr(data); if (character->char_server == -2) //Unknown server.. set them offline - remove_online_user(character->account_id); + login_remove_online_user(character->account_id); return 0; } -static int online_data_cleanup(int tid, unsigned int tick, int id, intptr_t data) -{ - online_db->foreach(online_db, online_data_cleanup_sub); - return 0; -} - - -//-------------------------------------------------------------------- -// Packet send to all char-servers, except one (wos: without our self) -//-------------------------------------------------------------------- -int charif_sendallwos(int sfd, uint8* buf, size_t len) -{ - int i, c; - - for( i = 0, c = 0; i < ARRAYLENGTH(ch_server); ++i ) - { - int fd = ch_server[i].fd; - if( session_isValid(fd) && fd != sfd ) - { - WFIFOHEAD(fd,len); - memcpy(WFIFOP(fd,0), buf, len); - WFIFOSET(fd,len); - ++c; - } - } - - return c; -} - - -/// Initializes a server structure. -void chrif_server_init(int id) -{ - memset(&ch_server[id], 0, sizeof(ch_server[id])); - ch_server[id].fd = -1; -} - - -/// Destroys a server structure. -void chrif_server_destroy(int id) -{ - if( ch_server[id].fd != -1 ) - { - do_close(ch_server[id].fd); - ch_server[id].fd = -1; - } -} - - -/// Resets all the data related to a server. -void chrif_server_reset(int id) -{ - online_db->foreach(online_db, online_db_setoffline, id); //Set all chars from this char server to offline. - chrif_server_destroy(id); - chrif_server_init(id); -} - - -/// Called when the connection to Char Server is disconnected. -void chrif_on_disconnect(int id) -{ - ShowStatus("Char-server '%s' has disconnected.\n", ch_server[id].name); - chrif_server_reset(id); -} - - -//----------------------------------------------------- -// periodic ip address synchronization -//----------------------------------------------------- -static int sync_ip_addresses(int tid, unsigned int tick, int id, intptr_t data) -{ - uint8 buf[2]; - ShowInfo("IP Sync in progress...\n"); - WBUFW(buf,0) = 0x2735; - charif_sendallwos(-1, buf, 2); - return 0; -} - - -//----------------------------------------------------- -// encrypted/unencrypted password check (from eApp) -//----------------------------------------------------- -bool check_encrypted(const char* str1, const char* str2, const char* passwd) -{ - char tmpstr[64+1], md5str[32+1]; - - safesnprintf(tmpstr, sizeof(tmpstr), "%s%s", str1, str2); - MD5_String(tmpstr, md5str); - - return (0==strcmp(passwd, md5str)); -} - -bool check_password(const char* md5key, int passwdenc, const char* passwd, const char* refpass) -{ - if(passwdenc == 0) - { - return (0==strcmp(passwd, refpass)); - } - else - { - // password mode set to 1 -> md5(md5key, refpass) enable with - // password mode set to 2 -> md5(refpass, md5key) enable with - - return ((passwdenc&0x01) && check_encrypted(md5key, refpass, passwd)) || - ((passwdenc&0x02) && check_encrypted(refpass, md5key, passwd)); - } -} - - -//-------------------------------------------- -// Test to know if an IP come from LAN or WAN. -//-------------------------------------------- -int lan_subnetcheck(uint32 ip) -{ - int i; - ARR_FIND( 0, subnet_count, i, (subnet[i].char_ip & subnet[i].mask) == (ip & subnet[i].mask) ); - return ( i < subnet_count ) ? subnet[i].char_ip : 0; -} - -//---------------------------------- -// Reading Lan Support configuration -//---------------------------------- -int login_lan_config_read(const char *lancfgName) -{ - FILE *fp; - int line_num = 0; - char line[1024], w1[64], w2[64], w3[64], w4[64]; - - if((fp = fopen(lancfgName, "r")) == NULL) { - ShowWarning("LAN Support configuration file is not found: %s\n", lancfgName); - return 1; - } - - while(fgets(line, sizeof(line), fp)) - { - line_num++; - if ((line[0] == '/' && line[1] == '/') || line[0] == '\n' || line[1] == '\n') - continue; - - if(sscanf(line,"%63[^:]: %63[^:]:%63[^:]:%63[^\r\n]", w1, w2, w3, w4) != 4) - { - ShowWarning("Error syntax of configuration file %s in line %d.\n", lancfgName, line_num); - continue; - } - - if( strcmpi(w1, "subnet") == 0 ) - { - subnet[subnet_count].mask = str2ip(w2); - subnet[subnet_count].char_ip = str2ip(w3); - subnet[subnet_count].map_ip = str2ip(w4); - - if( (subnet[subnet_count].char_ip & subnet[subnet_count].mask) != (subnet[subnet_count].map_ip & subnet[subnet_count].mask) ) - { - ShowError("%s: Configuration Error: The char server (%s) and map server (%s) belong to different subnetworks!\n", lancfgName, w3, w4); - continue; - } - - subnet_count++; - } - } - - if( subnet_count > 1 ) /* only useful if there is more than 1 available */ - ShowStatus("Read information about %d subnetworks.\n", subnet_count); - - fclose(fp); - return 0; -} - -//----------------------- -// Console Command Parser [Wizputer] -//----------------------- -int parse_console(const char* buf){ - char type[64]; - char command[64]; - int n=0; - - if( ( n = sscanf(buf, "%127[^:]:%255[^\n\r]", type, command) ) < 2 ){ - if((n = sscanf(buf, "%63[^\n]", type))<1) return -1; //nothing to do no arg - } - if( n != 2 ){ //end string - ShowNotice("Type: '%s'\n",type); - command[0] = '\0'; - } - else - ShowNotice("Type of command: '%s' || Command: '%s'\n",type,command); - - if( n == 2 ){ - if(strcmpi("server", type) == 0 ){ - if( strcmpi("shutdown", command) == 0 || strcmpi("exit", command) == 0 || strcmpi("quit", command) == 0 ){ - runflag = 0; - } - else if( strcmpi("alive", command) == 0 || strcmpi("status", command) == 0 ) - ShowInfo(CL_CYAN"Console: "CL_BOLD"I'm Alive."CL_RESET"\n"); - } - if( strcmpi("create",type) == 0 ) - { - char username[NAME_LENGTH], password[NAME_LENGTH], md5password[32+1], sex; //23+1 plaintext 32+1 md5 - bool md5 = 0; - if( sscanf(command, "%23s %23s %c", username, password, &sex) < 3 || strnlen(username, sizeof(username)) < 4 || strnlen(password, sizeof(password)) < 1 ){ - ShowWarning("Console: Invalid parameters for '%s'. Usage: %s \n", type, type); - return 0; - } - if( login_config.use_md5_passwds ){ - MD5_String(password,md5password); - md5 = 1; - } - if( mmo_auth_new(username,(md5?md5password:password), TOUPPER(sex), "0.0.0.0") != -1 ){ - ShowError("Console: Account creation failed.\n"); - return 0; - } - ShowStatus("Console: Account '%s' created successfully.\n", username); - } - } - else if( strcmpi("ers_report", type) == 0 ){ - ers_report(); - } - else if( strcmpi("help", type) == 0 ){ - ShowInfo("Available commands:\n"); - ShowInfo("\t server:shutdown => Stops the server.\n"); - ShowInfo("\t server:alive => Checks if the server is running.\n"); - ShowInfo("\t ers_report => Displays database usage.\n"); - ShowInfo("\t create: => Creates a new account.\n"); - } - else{ // commands with parameters - - - - } - - return 0; -} - -int chrif_send_accdata(int fd, uint32 aid) { - struct mmo_account acc; - time_t expiration_time = 0; - char email[40] = ""; - int group_id = 0; - char birthdate[10+1] = ""; - char pincode[PINCODE_LENGTH+1]; - int bank_vault = 0; - char isvip = false; - uint8 char_slots = MIN_CHARS, char_vip = 0; - - memset(pincode,0,PINCODE_LENGTH+1); - if( !accounts->load_num(accounts, &acc, aid) ) - return -1; - else { - safestrncpy(email, acc.email, sizeof(email)); - expiration_time = acc.expiration_time; - group_id = acc.group_id; - - safestrncpy(birthdate, acc.birthdate, sizeof(birthdate)); - safestrncpy(pincode, acc.pincode, sizeof(pincode)); - bank_vault = acc.bank_vault; -#ifdef VIP_ENABLE - char_vip = login_config.vip_sys.char_increase; - if( acc.vip_time > time(NULL) ) { - isvip = true; - char_slots = login_config.char_per_account + char_vip; - } else - char_slots = login_config.char_per_account; -#endif - } - - WFIFOHEAD(fd,79); - WFIFOW(fd,0) = 0x2717; - WFIFOL(fd,2) = aid; - safestrncpy((char*)WFIFOP(fd,6), email, 40); - WFIFOL(fd,46) = (uint32)expiration_time; - WFIFOB(fd,50) = (unsigned char)group_id; - WFIFOB(fd,51) = char_slots; - safestrncpy((char*)WFIFOP(fd,52), birthdate, 10+1); - safestrncpy((char*)WFIFOP(fd,63), pincode, 4+1 ); - WFIFOL(fd,68) = (uint32)acc.pincode_change; - WFIFOL(fd,72) = bank_vault; - WFIFOB(fd,76) = isvip; - WFIFOB(fd,77) = char_vip; - WFIFOB(fd,78) = MAX_CHAR_BILLING; //TODO create a config for this - WFIFOSET(fd,79); - return 0; -} - -int chrif_parse_reqaccdata(int fd, int cid, char *ip) { - if( RFIFOREST(fd) < 6 ) - return 0; - else { - uint32 aid = RFIFOL(fd,2); - RFIFOSKIP(fd,6); - if( chrif_send_accdata(fd,aid) < 0 ) - ShowNotice("Char-server '%s': account %d NOT found (ip: %s).\n", ch_server[cid].name, aid, ip); - } - return 0; -} - - -int chrif_sendvipdata(int fd, struct mmo_account acc, char isvip, char isgm, int mapfd) { -#ifdef VIP_ENABLE - WFIFOHEAD(fd,19); - WFIFOW(fd,0) = 0x2743; - WFIFOL(fd,2) = acc.account_id; - WFIFOL(fd,6) = (int)acc.vip_time; - WFIFOB(fd,10) = isvip; - WFIFOL(fd,11) = acc.group_id; //new group id - WFIFOL(fd,15) = isgm; - WFIFOL(fd,16) = mapfd; //link to mapserv - WFIFOSET(fd,20); - chrif_send_accdata(fd,acc.account_id); //refresh char with new setting -#endif - return 1; -} - /** - * Received a vip data reqest from char - * type is the query to perform - * &1 : Select info and update old_groupid - * &2 : Update vip time - * @param fd link to charserv - * @return 0 missing data, 1 succeeded + * Timered function to check if user is still connected. + * Launches every 600s by default. + * @param tid: timer id + * @param tick: tick of execution + * @param id: unused + * @param data: unused + * @return : 0 */ -int chrif_parse_reqvipdata(int fd) { -#ifdef VIP_ENABLE - if( RFIFOREST(fd) < 15 ) - return 0; - else { //request vip info - struct mmo_account acc; - int aid = RFIFOL(fd,2); - int8 type = RFIFOB(fd,6); - int32 timediff = RFIFOL(fd,7); - int mapfd = RFIFOL(fd,11); - RFIFOSKIP(fd,15); - - if( accounts->load_num(accounts, &acc, aid ) ) { - time_t now = time(NULL); - time_t vip_time = acc.vip_time; - bool isvip = false; - - if( acc.group_id > login_config.vip_sys.group ) { //Don't change group if it's higher. - chrif_sendvipdata(fd,acc,false,true,mapfd); - return 1; - } - if( type&2 ) { - if(!vip_time) vip_time = now; //new entry - vip_time += timediff; // set new duration - } - if( now < vip_time ) { //isvip - if(acc.group_id != login_config.vip_sys.group) //only upd this if we're not vip already - acc.old_group = acc.group_id; - acc.group_id = login_config.vip_sys.group; - acc.char_slots = login_config.char_per_account + login_config.vip_sys.char_increase; - isvip = true; - } else { //expired or @vip -xx - vip_time = 0; - if(acc.group_id == login_config.vip_sys.group) //prevent alteration in case account wasn't registered as vip yet - acc.group_id = acc.old_group; - acc.old_group = 0; - acc.char_slots = login_config.char_per_account; - } - acc.vip_time = vip_time; - accounts->save(accounts,&acc); - if( type&1 ) chrif_sendvipdata(fd,acc,isvip,false,mapfd); - } - } -#endif - return 1; -} - - -//-------------------------------- -// Packet parsing for char-servers -//-------------------------------- -int parse_fromchar(int fd){ - int j, id; - uint32 ipl; - char ip[16]; - - ARR_FIND( 0, ARRAYLENGTH(ch_server), id, ch_server[id].fd == fd ); - if( id == ARRAYLENGTH(ch_server) ){// not a char server - ShowDebug("parse_fromchar: Disconnecting invalid session #%d (is not a char-server)\n", fd); - set_eof(fd); - do_close(fd); - return 0; - } - - if( session[fd]->flag.eof ){ - do_close(fd); - ch_server[id].fd = -1; - chrif_on_disconnect(id); - return 0; - } - - ipl = ch_server[id].ip; - ip2str(ipl, ip); - - while( RFIFOREST(fd) >= 2 ){ - uint16 command = RFIFOW(fd,0); - - switch( command ){ - - case 0x2712: // request from char-server to authenticate an account - if( RFIFOREST(fd) < 23 ) - return 0; - else{ - struct auth_node* node; - int account_id = RFIFOL(fd,2); - uint32 login_id1 = RFIFOL(fd,6); - uint32 login_id2 = RFIFOL(fd,10); - uint8 sex = RFIFOB(fd,14); - //uint32 ip_ = ntohl(RFIFOL(fd,15)); - int request_id = RFIFOL(fd,19); - RFIFOSKIP(fd,23); - - node = (struct auth_node*)idb_get(auth_db, account_id); - if( runflag == LOGINSERVER_ST_RUNNING && - node != NULL && - node->account_id == account_id && - node->login_id1 == login_id1 && - node->login_id2 == login_id2 && - node->sex == sex_num2str(sex) /*&& - node->ip == ip_*/ ){// found - //ShowStatus("Char-server '%s': authentication of the account %d accepted (ip: %s).\n", server[id].name, account_id, ip); - - // send ack - WFIFOHEAD(fd,25); - WFIFOW(fd,0) = 0x2713; - WFIFOL(fd,2) = account_id; - WFIFOL(fd,6) = login_id1; - WFIFOL(fd,10) = login_id2; - WFIFOB(fd,14) = sex; - WFIFOB(fd,15) = 0;// ok - WFIFOL(fd,16) = request_id; - WFIFOL(fd,20) = node->version; - WFIFOB(fd,24) = node->clienttype; - WFIFOSET(fd,25); - - // each auth entry can only be used once - idb_remove(auth_db, account_id); - }else{// authentication not found - ShowStatus("Char-server '%s': authentication of the account %d REFUSED (ip: %s).\n", ch_server[id].name, account_id, ip); - WFIFOHEAD(fd,25); - WFIFOW(fd,0) = 0x2713; - WFIFOL(fd,2) = account_id; - WFIFOL(fd,6) = login_id1; - WFIFOL(fd,10) = login_id2; - WFIFOB(fd,14) = sex; - WFIFOB(fd,15) = 1;// auth failed - WFIFOL(fd,16) = request_id; - WFIFOL(fd,20) = 0; - WFIFOB(fd,24) = 0; - WFIFOSET(fd,25); - } - } - break; - - case 0x2714: - if( RFIFOREST(fd) < 6 ) - return 0; - else{ - int users = RFIFOL(fd,2); - RFIFOSKIP(fd,6); - - // how many users on world? (update) - if( ch_server[id].users != users ){ - ShowStatus("set users %s : %d\n", ch_server[id].name, users); - - ch_server[id].users = users; - } - } - break; - - case 0x2715: // request from char server to change e-email from default "a@a.com" - if (RFIFOREST(fd) < 46) - return 0; - else{ - struct mmo_account acc; - char email[40]; - - int account_id = RFIFOL(fd,2); - safestrncpy(email, (char*)RFIFOP(fd,6), 40); remove_control_chars(email); - RFIFOSKIP(fd,46); - - if( e_mail_check(email) == 0 ) - ShowNotice("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - e-mail is invalid (account: %d, ip: %s)\n", ch_server[id].name, account_id, ip); - else if( !accounts->load_num(accounts, &acc, account_id) || strcmp(acc.email, "a@a.com") == 0 || acc.email[0] == '\0' ) - ShowNotice("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - account doesn't exist or e-mail of account isn't default e-mail (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip); - else{ - memcpy(acc.email, email, 40); - ShowNotice("Char-server '%s': Create an e-mail on an account with a default e-mail (account: %d, new e-mail: %s, ip: %s).\n", ch_server[id].name, account_id, email, ip); - // Save - accounts->save(accounts, &acc); - } - } - break; - - case 0x2716: chrif_parse_reqaccdata(fd,id,ip); break; // request account data - - case 0x2719: // ping request from charserver - RFIFOSKIP(fd,2); - WFIFOHEAD(fd,2); - WFIFOW(fd,0) = 0x2718; - WFIFOSET(fd,2); - break; - - // Map server send information to change an email of an account via char-server - case 0x2722: // 0x2722 .L .40B .40B - if (RFIFOREST(fd) < 86) - return 0; - else{ - struct mmo_account acc; - char actual_email[40]; - char new_email[40]; - - int account_id = RFIFOL(fd,2); - safestrncpy(actual_email, (char*)RFIFOP(fd,6), 40); - safestrncpy(new_email, (char*)RFIFOP(fd,46), 40); - RFIFOSKIP(fd, 86); - - if( e_mail_check(actual_email) == 0 ) - ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual email is invalid (account: %d, ip: %s)\n", ch_server[id].name, account_id, ip); - else if( e_mail_check(new_email) == 0 ) - ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a invalid new e-mail (account: %d, ip: %s)\n", ch_server[id].name, account_id, ip); - else if( strcmpi(new_email, "a@a.com") == 0 ) - ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a default e-mail (account: %d, ip: %s)\n", ch_server[id].name, account_id, ip); - else if( !accounts->load_num(accounts, &acc, account_id) ) - ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but account doesn't exist (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip); - else if( strcmpi(acc.email, actual_email) != 0 ) - ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual e-mail is incorrect (account: %d (%s), actual e-mail: %s, proposed e-mail: %s, ip: %s).\n", ch_server[id].name, account_id, acc.userid, acc.email, actual_email, ip); - else{ - safestrncpy(acc.email, new_email, 40); - ShowNotice("Char-server '%s': Modify an e-mail on an account (@email GM command) (account: %d (%s), new e-mail: %s, ip: %s).\n", ch_server[id].name, account_id, acc.userid, new_email, ip); - // Save - accounts->save(accounts, &acc); - } - } - break; - - case 0x2724: // Receiving an account state update request from a map-server (relayed via char-server) - if (RFIFOREST(fd) < 10) - return 0; - else{ - struct mmo_account acc; - - int account_id = RFIFOL(fd,2); - unsigned int state = RFIFOL(fd,6); - RFIFOSKIP(fd,10); - - if( !accounts->load_num(accounts, &acc, account_id) ) - ShowNotice("Char-server '%s': Error of Status change (account: %d not found, suggested status %d, ip: %s).\n", ch_server[id].name, account_id, state, ip); - else if( acc.state == state ) - ShowNotice("Char-server '%s': Error of Status change - actual status is already the good status (account: %d, status %d, ip: %s).\n", ch_server[id].name, account_id, state, ip); - else{ - ShowNotice("Char-server '%s': Status change (account: %d, new status %d, ip: %s).\n", ch_server[id].name, account_id, state, ip); - - acc.state = state; - // Save - accounts->save(accounts, &acc); - - // notify other servers - if (state != 0){ - uint8 buf[11]; - WBUFW(buf,0) = 0x2731; - WBUFL(buf,2) = account_id; - WBUFB(buf,6) = 0; // 0: change of state, 1: ban - WBUFL(buf,7) = state; // status or final date of a banishment - charif_sendallwos(-1, buf, 11); - } - } - } - break; - - case 0x2725: // Receiving of map-server via char-server a ban request - if (RFIFOREST(fd) < 10) - return 0; - else{ - struct mmo_account acc; - - int account_id = RFIFOL(fd,2); - int timediff = RFIFOL(fd,6); - RFIFOSKIP(fd,10); - - if( !accounts->load_num(accounts, &acc, account_id) ) - ShowNotice("Char-server '%s': Error of ban request (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip); - else{ - time_t timestamp; - if (acc.unban_time == 0 || acc.unban_time < time(NULL)) - timestamp = time(NULL); // new ban - else - timestamp = acc.unban_time; // add to existing ban - timestamp += timediff; - if (timestamp == -1) - ShowNotice("Char-server '%s': Error of ban request (account: %d, invalid date, ip: %s).\n", ch_server[id].name, account_id, ip); - else if( timestamp <= time(NULL) || timestamp == 0 ) - ShowNotice("Char-server '%s': Error of ban request (account: %d, new date unbans the account, ip: %s).\n", ch_server[id].name, account_id, ip); - else{ - uint8 buf[11]; - char tmpstr[24]; - timestamp2string(tmpstr, sizeof(tmpstr), timestamp, login_config.date_format); - ShowNotice("Char-server '%s': Ban request (account: %d, new final date of banishment: %d (%s), ip: %s).\n", ch_server[id].name, account_id, timestamp, tmpstr, ip); - - acc.unban_time = timestamp; - - // Save - accounts->save(accounts, &acc); - - WBUFW(buf,0) = 0x2731; - WBUFL(buf,2) = account_id; - WBUFB(buf,6) = 1; // 0: change of status, 1: ban - WBUFL(buf,7) = (uint32)timestamp; // status or final date of a banishment - charif_sendallwos(-1, buf, 11); - } - } - } - break; - - case 0x2727: // Change of sex (sex is reversed) - if( RFIFOREST(fd) < 6 ) - return 0; - else{ - struct mmo_account acc; - - int account_id = RFIFOL(fd,2); - RFIFOSKIP(fd,6); - - if( !accounts->load_num(accounts, &acc, account_id) ) - ShowNotice("Char-server '%s': Error of sex change (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip); - else if( acc.sex == 'S' ) - ShowNotice("Char-server '%s': Error of sex change - account to change is a Server account (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip); - else{ - unsigned char buf[7]; - char sex = ( acc.sex == 'M' ) ? 'F' : 'M'; //Change gender - - ShowNotice("Char-server '%s': Sex change (account: %d, new sex %c, ip: %s).\n", ch_server[id].name, account_id, sex, ip); - - acc.sex = sex; - // Save - accounts->save(accounts, &acc); - - // announce to other servers - WBUFW(buf,0) = 0x2723; - WBUFL(buf,2) = account_id; - WBUFB(buf,6) = sex_str2num(sex); - charif_sendallwos(-1, buf, 7); - } - } - break; - - case 0x2728: // We receive account_reg2 from a char-server, and we send them to other map-servers. - if( RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2) ) - return 0; - else{ - struct mmo_account acc; - - int account_id = RFIFOL(fd,4); - - if( !accounts->load_num(accounts, &acc, account_id) ) - ShowStatus("Char-server '%s': receiving (from the char-server) of account_reg2 (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip); - else{ - int len; - int p; - ShowNotice("char-server '%s': receiving (from the char-server) of account_reg2 (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip); - for( j = 0, p = 13; j < ACCOUNT_REG2_NUM && p < RFIFOW(fd,2); ++j ){ - sscanf((char*)RFIFOP(fd,p), "%31c%n", acc.account_reg2[j].str, &len); - acc.account_reg2[j].str[len]='\0'; - p +=len+1; //+1 to skip the '\0' between strings. - sscanf((char*)RFIFOP(fd,p), "%255c%n", acc.account_reg2[j].value, &len); - acc.account_reg2[j].value[len]='\0'; - p +=len+1; - remove_control_chars(acc.account_reg2[j].str); - remove_control_chars(acc.account_reg2[j].value); - } - acc.account_reg2_num = j; - - // Save - accounts->save(accounts, &acc); - - // Sending information towards the other char-servers. - RFIFOW(fd,0) = 0x2729;// reusing read buffer - charif_sendallwos(fd, RFIFOP(fd,0), RFIFOW(fd,2)); - } - RFIFOSKIP(fd,RFIFOW(fd,2)); - } - break; - - case 0x272a: // Receiving of map-server via char-server an unban request - if( RFIFOREST(fd) < 6 ) - return 0; - else{ - struct mmo_account acc; - - int account_id = RFIFOL(fd,2); - RFIFOSKIP(fd,6); - - if( !accounts->load_num(accounts, &acc, account_id) ) - ShowNotice("Char-server '%s': Error of UnBan request (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip); - else if( acc.unban_time == 0 ) - ShowNotice("Char-server '%s': Error of UnBan request (account: %d, no change for unban date, ip: %s).\n", ch_server[id].name, account_id, ip); - else{ - ShowNotice("Char-server '%s': UnBan request (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip); - acc.unban_time = 0; - accounts->save(accounts, &acc); - } - } - break; - - case 0x272b: // Set account_id to online [Wizputer] - if( RFIFOREST(fd) < 6 ) - return 0; - add_online_user(id, RFIFOL(fd,2)); - RFIFOSKIP(fd,6); - break; - - case 0x272c: // Set account_id to offline [Wizputer] - if( RFIFOREST(fd) < 6 ) - return 0; - remove_online_user(RFIFOL(fd,2)); - RFIFOSKIP(fd,6); - break; - - case 0x272d: // Receive list of all online accounts. [Skotlex] - if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) - return 0; - else{ - uint32 i, users; - online_db->foreach(online_db, online_db_setoffline, id); //Set all chars from this char-server offline first - users = RFIFOW(fd,4); - for (i = 0; i < users; i++) { - int aid = RFIFOL(fd,6+i*4); - struct online_login_data *p = idb_ensure(online_db, aid, create_online_user); - p->char_server = id; - if (p->waiting_disconnect != INVALID_TIMER){ - delete_timer(p->waiting_disconnect, waiting_disconnect_timer); - p->waiting_disconnect = INVALID_TIMER; - } - } - RFIFOSKIP(fd,RFIFOW(fd,2)); - } - break; - - case 0x272e: //Request account_reg2 for a character. - if (RFIFOREST(fd) < 10) - return 0; - else{ - struct mmo_account acc; - size_t off; - - int account_id = RFIFOL(fd,2); - int char_id = RFIFOL(fd,6); - RFIFOSKIP(fd,10); - - WFIFOHEAD(fd,ACCOUNT_REG2_NUM*sizeof(struct global_reg)); - WFIFOW(fd,0) = 0x2729; - WFIFOL(fd,4) = account_id; - WFIFOL(fd,8) = char_id; - WFIFOB(fd,12) = 1; //Type 1 for Account2 registry - - off = 13; - if( accounts->load_num(accounts, &acc, account_id) ){ - for( j = 0; j < acc.account_reg2_num; j++ ){ - if( acc.account_reg2[j].str[0] != '\0' ){ - off += sprintf((char*)WFIFOP(fd,off), "%s", acc.account_reg2[j].str)+1; //We add 1 to consider the '\0' in place. - off += sprintf((char*)WFIFOP(fd,off), "%s", acc.account_reg2[j].value)+1; - } - } - } - - WFIFOW(fd,2) = (uint16)off; - WFIFOSET(fd,WFIFOW(fd,2)); - } - break; - - case 0x2736: // WAN IP update from char-server - if( RFIFOREST(fd) < 6 ) - return 0; - ch_server[id].ip = ntohl(RFIFOL(fd,2)); - ShowInfo("Updated IP of Server #%d to %d.%d.%d.%d.\n",id, CONVIP(ch_server[id].ip)); - RFIFOSKIP(fd,6); - break; - - case 0x2737: //Request to set all offline. - ShowInfo("Setting accounts from char-server %d offline.\n", id); - online_db->foreach(online_db, online_db_setoffline, id); - RFIFOSKIP(fd,2); - break; - - case 0x2738: //Change PIN Code for a account - if( RFIFOREST(fd) < 11 ) - return 0; - else{ - struct mmo_account acc; - if( accounts->load_num(accounts, &acc, RFIFOL(fd,2) ) ){ - strncpy( acc.pincode, (char*)RFIFOP(fd,6), 5 ); - acc.pincode_change = time( NULL ); - accounts->save(accounts, &acc); - } - RFIFOSKIP(fd,11); - } - break; - - case 0x2739: // PIN Code was entered wrong too often - if( RFIFOREST(fd) < 6 ) - return 0; - else{ - struct mmo_account acc; - - if( accounts->load_num(accounts, &acc, RFIFOL(fd,2) ) ){ - struct online_login_data* ld; - - ld = (struct online_login_data*)idb_get(online_db,acc.account_id); - - if( ld == NULL ) - return 0; - - login_log( host2ip(acc.last_ip), acc.userid, 100, "PIN Code check failed" ); - } - - remove_online_user(acc.account_id); - - RFIFOSKIP(fd,6); - } - break; - - case 0x2740: // req upd bank_vault - if( RFIFOREST(fd) < 11 ) - return 0; - else{ - struct mmo_account acc; - - int account_id = RFIFOL(fd,2); - char type = RFIFOB(fd,6); - int32 data = RFIFOL(fd,7); - RFIFOSKIP(fd,11); - - if( !accounts->load_num(accounts, &acc, account_id) ) - ShowNotice("Char-server '%s': Error on banking (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip); - else{ - unsigned char buf[12]; - if(type==2){ // upd and Save - acc.bank_vault = data; - accounts->save(accounts, &acc); - WBUFB(buf,10) = 1; - } else { - WBUFB(buf,10) = 0; - } - // announce to other servers - WBUFW(buf,0) = 0x2741; - WBUFL(buf,2) = account_id; - WBUFL(buf,6) = acc.bank_vault; - charif_sendallwos(-1, buf, 11); - } - } - break; - - case 0x2742: chrif_parse_reqvipdata(fd); break; //Vip sys - - default: - ShowError("parse_fromchar: Unknown packet 0x%x from a char-server! Disconnecting!\n", command); - set_eof(fd); - return 0; - } // switch - } // while - +static int login_online_data_cleanup(int tid, unsigned int tick, int id, intptr_t data) { + online_db->foreach(online_db, login_online_data_cleanup_sub); return 0; } - -//------------------------------------- -// Make new account -//------------------------------------- -int mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip) { +/** + * Create a new account and save it in db/sql. + * @param userid: string for user login + * @param pass: string for user pass + * @param sex: should be M|F|S (todo make an enum ?) + * @param last_ip: + * @return : + * -1: success + * 0: unregistered id (wrong sex fail to create in db); + * 1: incorrect pass or userid (userid|pass too short or already exist); + * 3: registration limit exceeded; + */ +int login_mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip) { static int num_regs = 0; // registration counter static unsigned int new_reg_tick = 0; unsigned int tick = gettick(); @@ -1082,7 +264,7 @@ int mmo_auth_new(const char* userid, const char* pass, const char sex, const cha //Account Registration Flood Protection by [Kevin] if( new_reg_tick == 0 ) new_reg_tick = gettick(); - if( DIFF_TICK(tick, new_reg_tick) < 0 && num_regs >= allowed_regs ) { + if( DIFF_TICK(tick, new_reg_tick) < 0 && num_regs >= login_config.allowed_regs ) { ShowNotice("Account registration denied (registration limit exceeded)\n"); return 3; } @@ -1125,17 +307,28 @@ int mmo_auth_new(const char* userid, const char* pass, const char sex, const cha if( DIFF_TICK(tick, new_reg_tick) > 0 ) {// Update the registration check. num_regs = 0; - new_reg_tick = tick + time_allowed*1000; + new_reg_tick = tick + login_config.time_allowed*1000; } ++num_regs; return -1; } -//----------------------------------------------------- -// Check/authentication of a connection -//----------------------------------------------------- -int mmo_auth(struct login_session_data* sd, bool isServer) { +/** + * Check/authentication of a connection. + * @param sd: string (atm:md5key or dbpass) + * @param isServer: string (atm:md5key or dbpass) + * @return : + * -1: success + * 0: unregistered id; + * 1: incorrect pass; + * 2: expired id + * 3: blacklisted (or registration limit exceeded if new acc); + * 5: invalid client_version|hash; + * 6: banned + * x: acc state (TODO document me deeper) + */ +int login_mmo_auth(struct login_session_data* sd, bool isServer) { struct mmo_account acc; int len; @@ -1162,8 +355,11 @@ int mmo_auth(struct login_session_data* sd, bool isServer) { } //Client Version check - if( login_config.check_client_version && sd->version != login_config.client_version_to_connect ) + if( login_config.check_client_version && sd->version != login_config.client_version_to_connect ){ + ShowNotice("Invalid version (account: '%s', auth_vers: '%d', received version: '%d', ip: %s)\n", + sd->userid, login_config.client_version_to_connect, sd->version, ip); return 5; + } len = strnlen(sd->userid, NAME_LENGTH); @@ -1174,12 +370,11 @@ int mmo_auth(struct login_session_data* sd, bool isServer) { sd->userid[len-2] == '_' && memchr("FfMm", sd->userid[len-1], 4) ) // _M/_F suffix { int result; - // remove the _M/_F suffix len -= 2; sd->userid[len] = '\0'; - result = mmo_auth_new(sd->userid, sd->passwd, TOUPPER(sd->userid[len+1]), ip); + result = login_mmo_auth_new(sd->userid, sd->passwd, TOUPPER(sd->userid[len+1]), ip); if( result != -1 ) return result;// Failed to make account. [Skotlex]. } @@ -1190,7 +385,7 @@ int mmo_auth(struct login_session_data* sd, bool isServer) { return 0; // 0 = Unregistered ID } - if( !check_password(sd->md5key, sd->passwdenc, sd->passwd, acc.pass) ) { + if( !login_check_password(sd->md5key, sd->passwdenc, sd->passwd, acc.pass) ) { ShowNotice("Invalid password (account: '%s', pass: '%s', received pass: '%s', ip: %s)\n", sd->userid, acc.pass, sd->passwd, ip); return 1; // 1 = Incorrect Password } @@ -1268,505 +463,133 @@ int mmo_auth(struct login_session_data* sd, bool isServer) { return -1; // account OK } -void login_auth_ok(struct login_session_data* sd) -{ - int fd = sd->fd; - uint32 ip = session[fd]->client_addr; +/** + * Sub function of login_check_password. + * Checking if password matches the one in db hashed with client md5key. + * Test if(md5(str1+str2)==passwd). + * @param str1: string (atm:md5key or dbpass) + * @param str2: string (atm:md5key or dbpass) + * @param passwd: pass to check + * @return true if matching else false + */ +bool login_check_encrypted(const char* str1, const char* str2, const char* passwd) { + char tmpstr[64+1], md5str[32+1]; - uint8 server_num, n; - uint32 subnet_char_ip; - struct auth_node* node; + safesnprintf(tmpstr, sizeof(tmpstr), "%s%s", str1, str2); + MD5_String(tmpstr, md5str); + + return (0==strcmp(passwd, md5str)); +} + +/** + * Verify if a password is correct. + * @param md5key: md5key of client + * @param passwdenc: encode key of client + * @param passwd: pass to check + * @param refpass: pass register in db + * @return true if matching else false + */ +bool login_check_password(const char* md5key, int passwdenc, const char* passwd, const char* refpass) { + if(passwdenc == 0){ + return (0==strcmp(passwd, refpass)); + } + else { + // password mode set to 1 -> md5(md5key, refpass) enable with + // password mode set to 2 -> md5(refpass, md5key) enable with + return ((passwdenc&0x01) && login_check_encrypted(md5key, refpass, passwd)) || + ((passwdenc&0x02) && login_check_encrypted(refpass, md5key, passwd)); + } +} + +/** + * Test to determine if an IP come from LAN or WAN. + * @param ip: ip to check if in auth network + * @return 0 if from wan, or subnet_char_ip if lan + */ +int lan_subnetcheck(uint32 ip) { int i; + ARR_FIND( 0, subnet_count, i, (subnet[i].char_ip & subnet[i].mask) == (ip & subnet[i].mask) ); + return ( i < subnet_count ) ? subnet[i].char_ip : 0; +} - if( runflag != LOGINSERVER_ST_RUNNING ) + + + +/// Msg_conf tayloring +int login_msg_config_read(char *cfgName){ + return _msg_config_read(cfgName,LOGIN_MAX_MSG,msg_table); +} +const char* login_msg_txt(int msg_number){ + return _msg_txt(msg_number,LOGIN_MAX_MSG,msg_table); +} +void login_do_final_msg(void){ + _do_final_msg(LOGIN_MAX_MSG,msg_table); +} + + + + +/// Set and read Configurations + +/** + * Reading Lan Support configuration. + * @param lancfgName: Name of the lan configuration (could be fullpath) + * @return 0:success, 1:failure (file not found|readable) + */ +int login_lan_config_read(const char *lancfgName) { + FILE *fp; + int line_num = 0, s_subnet=ARRAYLENGTH(subnet); + char line[1024], w1[64], w2[64], w3[64], w4[64]; + + if((fp = fopen(lancfgName, "r")) == NULL) { + ShowWarning("LAN Support configuration file is not found: %s\n", lancfgName); + return 1; + } + + while(fgets(line, sizeof(line), fp)) { - // players can only login while running - WFIFOHEAD(fd,3); - WFIFOW(fd,0) = 0x81; - WFIFOB(fd,2) = 1;// server closed - WFIFOSET(fd,3); - return; - } - - if( login_config.group_id_to_connect >= 0 && sd->group_id != login_config.group_id_to_connect ) { - ShowStatus("Connection refused: the required group id for connection is %d (account: %s, group: %d).\n", login_config.group_id_to_connect, sd->userid, sd->group_id); - WFIFOHEAD(fd,3); - WFIFOW(fd,0) = 0x81; - WFIFOB(fd,2) = 1; // 01 = Server closed - WFIFOSET(fd,3); - return; - } else if( login_config.min_group_id_to_connect >= 0 && login_config.group_id_to_connect == -1 && sd->group_id < login_config.min_group_id_to_connect ) { - ShowStatus("Connection refused: the minium group id required for connection is %d (account: %s, group: %d).\n", login_config.min_group_id_to_connect, sd->userid, sd->group_id); - WFIFOHEAD(fd,3); - WFIFOW(fd,0) = 0x81; - WFIFOB(fd,2) = 1; // 01 = Server closed - WFIFOSET(fd,3); - return; - } - - server_num = 0; - for( i = 0; i < ARRAYLENGTH(ch_server); ++i ) - if( session_isActive(ch_server[i].fd) ) - server_num++; - - if( server_num == 0 ) - {// if no char-server, don't send void list of servers, just disconnect the player with proper message - ShowStatus("Connection refused: there is no char-server online (account: %s).\n", sd->userid); - WFIFOHEAD(fd,3); - WFIFOW(fd,0) = 0x81; - WFIFOB(fd,2) = 1; // 01 = Server closed - WFIFOSET(fd,3); - return; - } - - { - struct online_login_data* data = (struct online_login_data*)idb_get(online_db, sd->account_id); - if( data ) - {// account is already marked as online! - if( data->char_server > -1 ) - {// Request char servers to kick this account out. [Skotlex] - uint8 buf[6]; - ShowNotice("User '%s' is already online - Rejected.\n", sd->userid); - WBUFW(buf,0) = 0x2734; - WBUFL(buf,2) = sd->account_id; - charif_sendallwos(-1, buf, 6); - if( data->waiting_disconnect == INVALID_TIMER ) - data->waiting_disconnect = add_timer(gettick()+AUTH_TIMEOUT, waiting_disconnect_timer, sd->account_id, 0); - - WFIFOHEAD(fd,3); - WFIFOW(fd,0) = 0x81; - WFIFOB(fd,2) = 8; // 08 = Server still recognizes your last login - WFIFOSET(fd,3); - return; - } - else - if( data->char_server == -1 ) - {// client has authed but did not access char-server yet - // wipe previous session - idb_remove(auth_db, sd->account_id); - remove_online_user(sd->account_id); - data = NULL; - } - } - } - - login_log(ip, sd->userid, 100, "login ok"); - ShowStatus("Connection of the account '%s' accepted.\n", sd->userid); - - WFIFOHEAD(fd,47+32*server_num); - WFIFOW(fd,0) = 0x69; - WFIFOW(fd,2) = 47+32*server_num; - WFIFOL(fd,4) = sd->login_id1; - WFIFOL(fd,8) = sd->account_id; - WFIFOL(fd,12) = sd->login_id2; - WFIFOL(fd,16) = 0; // in old version, that was for ip (not more used) - //memcpy(WFIFOP(fd,20), sd->lastlogin, 24); // in old version, that was for name (not more used) - memset(WFIFOP(fd,20), 0, 24); - WFIFOW(fd,44) = 0; // unknown - WFIFOB(fd,46) = sex_str2num(sd->sex); - for( i = 0, n = 0; i < ARRAYLENGTH(ch_server); ++i ) - { - if( !session_isValid(ch_server[i].fd) ) + line_num++; + if ((line[0] == '/' && line[1] == '/') || line[0] == '\n' || line[1] == '\n') continue; - subnet_char_ip = lan_subnetcheck(ip); // Advanced subnet check [LuzZza] - WFIFOL(fd,47+n*32) = htonl((subnet_char_ip) ? subnet_char_ip : ch_server[i].ip); - WFIFOW(fd,47+n*32+4) = ntows(htons(ch_server[i].port)); // [!] LE byte order here [!] - memcpy(WFIFOP(fd,47+n*32+6), ch_server[i].name, 20); - WFIFOW(fd,47+n*32+26) = ch_server[i].users; - WFIFOW(fd,47+n*32+28) = ch_server[i].type; - WFIFOW(fd,47+n*32+30) = ch_server[i].new_; - n++; - } - WFIFOSET(fd,47+32*server_num); - - // create temporary auth entry - CREATE(node, struct auth_node, 1); - node->account_id = sd->account_id; - node->login_id1 = sd->login_id1; - node->login_id2 = sd->login_id2; - node->sex = sd->sex; - node->ip = ip; - node->version = sd->version; - node->clienttype = sd->clienttype; - idb_put(auth_db, sd->account_id, node); - - { - struct online_login_data* data; - - // mark client as 'online' - data = add_online_user(-1, sd->account_id); - - // schedule deletion of this node - data->waiting_disconnect = add_timer(gettick()+AUTH_TIMEOUT, waiting_disconnect_timer, sd->account_id, 0); - } -} - -/* Log the result of a failed connection attempt by sd - * result: nb (msg define in conf) - 0 = Unregistered ID - 1 = Incorrect Password - 2 = This ID is expired - 3 = Rejected from Server - 4 = You have been blocked by the GM Team - 5 = Your Game's EXE file is not the latest version - 6 = Your are Prohibited to log in until %s - 7 = Server is jammed due to over populated - 8 = No more accounts may be connected from this company - 9 = MSI_REFUSE_BAN_BY_DBA - 10 = MSI_REFUSE_EMAIL_NOT_CONFIRMED - 11 = MSI_REFUSE_BAN_BY_GM - 12 = MSI_REFUSE_TEMP_BAN_FOR_DBWORK - 13 = MSI_REFUSE_SELF_LOCK - 14 = MSI_REFUSE_NOT_PERMITTED_GROUP - 15 = MSI_REFUSE_NOT_PERMITTED_GROUP - 99 = This ID has been totally erased - 100 = Login information remains at %s - 101 = Account has been locked for a hacking investigation. Please contact the GM Team for more information - 102 = This account has been temporarily prohibited from login due to a bug-related investigation - 103 = This character is being deleted. Login is temporarily unavailable for the time being - 104 = This character is being deleted. Login is temporarily unavailable for the time being - default = Unknown Error. - */ -void login_auth_failed(struct login_session_data* sd, int result) -{ - int fd = sd->fd; - uint32 ip = session[fd]->client_addr; - - if (login_config.log_login) - { - if(result >= 0 && result <= 15) - login_log(ip, sd->userid, result, msg_txt(result)); - else if(result >= 99 && result <= 104) - login_log(ip, sd->userid, result, msg_txt(result-83)); //-83 offset - else - login_log(ip, sd->userid, result, msg_txt(22)); //unknow error - } - - if( result == 1 && login_config.dynamic_pass_failure_ban ) - ipban_log(ip); // log failed password attempt - -#if PACKETVER >= 20120000 /* not sure when this started */ - WFIFOHEAD(fd,26); - WFIFOW(fd,0) = 0x83e; - WFIFOL(fd,2) = result; - if( result != 6 ) - memset(WFIFOP(fd,6), '\0', 20); - else { // 6 = Your are Prohibited to log in until %s - struct mmo_account acc; - time_t unban_time = ( accounts->load_str(accounts, &acc, sd->userid) ) ? acc.unban_time : 0; - timestamp2string((char*)WFIFOP(fd,6), 20, unban_time, login_config.date_format); - } - WFIFOSET(fd,26); -#else - WFIFOHEAD(fd,23); - WFIFOW(fd,0) = 0x6a; - WFIFOB(fd,2) = (uint8)result; - if( result != 6 ) - memset(WFIFOP(fd,3), '\0', 20); - else { // 6 = Your are Prohibited to log in until %s - struct mmo_account acc; - time_t unban_time = ( accounts->load_str(accounts, &acc, sd->userid) ) ? acc.unban_time : 0; - timestamp2string((char*)WFIFOP(fd,3), 20, unban_time, login_config.date_format); - } - WFIFOSET(fd,23); -#endif -} - - -//---------------------------------------------------------------------------------------- -// Default packet parsing (normal players or char-server connection requests) -//---------------------------------------------------------------------------------------- -int parse_login(int fd) -{ - struct login_session_data* sd = (struct login_session_data*)session[fd]->session_data; - int result; - - char ip[16]; - uint32 ipl = session[fd]->client_addr; - ip2str(ipl, ip); - - if( session[fd]->flag.eof ) - { - ShowInfo("Closed connection from '"CL_WHITE"%s"CL_RESET"'.\n", ip); - do_close(fd); - return 0; - } - - if( sd == NULL ) - { - // Perform ip-ban check - if( login_config.ipban && ipban_check(ipl) ) + if(sscanf(line,"%63[^:]: %63[^:]:%63[^:]:%63[^\r\n]", w1, w2, w3, w4) != 4) { - ShowStatus("Connection refused: IP isn't authorised (deny/allow, ip: %s).\n", ip); - login_log(ipl, "unknown", -3, "ip banned"); - WFIFOHEAD(fd,23); - WFIFOW(fd,0) = 0x6a; - WFIFOB(fd,2) = 3; // 3 = Rejected from Server - WFIFOSET(fd,23); - set_eof(fd); - return 0; + ShowWarning("Error syntax of configuration file %s in line %d.\n", lancfgName, line_num); + continue; } - // create a session for this new connection - CREATE(session[fd]->session_data, struct login_session_data, 1); - sd = (struct login_session_data*)session[fd]->session_data; - sd->fd = fd; - } - - while( RFIFOREST(fd) >= 2 ) - { - uint16 command = RFIFOW(fd,0); - - switch( command ) - { - - case 0x0200: // New alive packet: structure: 0x200 .24B. used to verify if client is always alive. - if (RFIFOREST(fd) < 26) - return 0; - RFIFOSKIP(fd,26); - break; - - // client md5 hash (binary) - case 0x0204: // S 0204 .16B (kRO 2004-05-31aSakexe langtype 0 and 6) - if (RFIFOREST(fd) < 18) - return 0; - - sd->has_client_hash = 1; - memcpy(sd->client_hash, RFIFOP(fd, 2), 16); - - RFIFOSKIP(fd,18); - break; - - // request client login (raw password) - case 0x0064: // S 0064 .L .24B .24B .B - case 0x0277: // S 0277 .L .24B .24B .B .16B .13B - case 0x02b0: // S 02b0 .L .24B .24B .B .16B .13B .B - // request client login (md5-hashed password) - case 0x01dd: // S 01dd .L .24B .16B .B - case 0x01fa: // S 01fa .L .24B .16B .B .B(index of the connection in the clientinfo file (+10 if the command-line contains "pc")) - case 0x027c: // S 027c .L .24B .16B .B .13B(junk) - case 0x0825: // S 0825 .W .L .B .24B .27B .17B .15B .(packetsize - 0x5C)B - { - size_t packet_len = RFIFOREST(fd); - - if( (command == 0x0064 && packet_len < 55) - || (command == 0x0277 && packet_len < 84) - || (command == 0x02b0 && packet_len < 85) - || (command == 0x01dd && packet_len < 47) - || (command == 0x01fa && packet_len < 48) - || (command == 0x027c && packet_len < 60) - || (command == 0x0825 && (packet_len < 4 || packet_len < RFIFOW(fd, 2))) ) - return 0; - } - { - uint32 version; - char username[NAME_LENGTH]; - char password[NAME_LENGTH]; - unsigned char passhash[16]; - uint8 clienttype; - bool israwpass = (command==0x0064 || command==0x0277 || command==0x02b0 || command == 0x0825); - - // Shinryo: For the time being, just use token as password. - if(command == 0x0825) - { - char *accname = (char *)RFIFOP(fd, 9); - char *token = (char *)RFIFOP(fd, 0x5C); - size_t uAccLen = strlen(accname); - size_t uTokenLen = RFIFOREST(fd) - 0x5C; - - version = RFIFOL(fd,4); - - if(uAccLen > NAME_LENGTH - 1 || uAccLen == 0 || uTokenLen > NAME_LENGTH - 1 || uTokenLen == 0) - { - login_auth_failed(sd, 3); - return 0; - } - - safestrncpy(username, accname, uAccLen + 1); - safestrncpy(password, token, uTokenLen + 1); - clienttype = RFIFOB(fd, 8); + if( strcmpi(w1, "subnet") == 0 ){ + if(subnet_count>=s_subnet) { //We skip instead of break in case we want to add other conf in that file. + ShowError("%s: Too many subnets defined, skipping line %d...\n", lancfgName, line_num); + continue; } - else - { - version = RFIFOL(fd,2); - safestrncpy(username, (const char*)RFIFOP(fd,6), NAME_LENGTH); - if( israwpass ) - { - safestrncpy(password, (const char*)RFIFOP(fd,30), NAME_LENGTH); - clienttype = RFIFOB(fd,54); - } - else - { - memcpy(passhash, RFIFOP(fd,30), 16); - clienttype = RFIFOB(fd,46); - } - } - RFIFOSKIP(fd,RFIFOREST(fd)); // assume no other packet was sent + subnet[subnet_count].mask = str2ip(w2); + subnet[subnet_count].char_ip = str2ip(w3); + subnet[subnet_count].map_ip = str2ip(w4); - sd->clienttype = clienttype; - sd->version = version; - safestrncpy(sd->userid, username, NAME_LENGTH); - if( israwpass ) + if( (subnet[subnet_count].char_ip & subnet[subnet_count].mask) != (subnet[subnet_count].map_ip & subnet[subnet_count].mask) ) { - ShowStatus("Request for connection of %s (ip: %s) version=%d\n", sd->userid, ip,sd->version); - safestrncpy(sd->passwd, password, NAME_LENGTH); - if( login_config.use_md5_passwds ) - MD5_String(sd->passwd, sd->passwd); - sd->passwdenc = 0; - } - else - { - ShowStatus("Request for connection (passwdenc mode) of %s (ip: %s) version=%d\n", sd->userid, ip,sd->version); - bin2hex(sd->passwd, passhash, 16); // raw binary data here! - sd->passwdenc = PASSWORDENC; + ShowError("%s: Configuration Error: The char server (%s) and map server (%s) belong to different subnetworks!\n", lancfgName, w3, w4); + continue; } - if( sd->passwdenc != 0 && login_config.use_md5_passwds ) - { - login_auth_failed(sd, 3); // send "rejected from server" - return 0; - } - - result = mmo_auth(sd, false); - - if( result == -1 ) - login_auth_ok(sd); - else - login_auth_failed(sd, result); - } - break; - - case 0x01db: // Sending request of the coding key - RFIFOSKIP(fd,2); - { - memset(sd->md5key, '\0', sizeof(sd->md5key)); - sd->md5keylen = (uint16)(12 + rnd() % 4); - MD5_Salt(sd->md5keylen, sd->md5key); - - WFIFOHEAD(fd,4 + sd->md5keylen); - WFIFOW(fd,0) = 0x01dc; - WFIFOW(fd,2) = 4 + sd->md5keylen; - memcpy(WFIFOP(fd,4), sd->md5key, sd->md5keylen); - WFIFOSET(fd,WFIFOW(fd,2)); - } - break; - - case 0x2710: // Connection request of a char-server - if (RFIFOREST(fd) < 86) - return 0; - { - char server_name[20]; - char message[256]; - uint32 server_ip; - uint16 server_port; - uint16 type; - uint16 new_; - - safestrncpy(sd->userid, (char*)RFIFOP(fd,2), NAME_LENGTH); - safestrncpy(sd->passwd, (char*)RFIFOP(fd,26), NAME_LENGTH); - if( login_config.use_md5_passwds ) - MD5_String(sd->passwd, sd->passwd); - sd->passwdenc = 0; - sd->version = login_config.client_version_to_connect; // hack to skip version check - server_ip = ntohl(RFIFOL(fd,54)); - server_port = ntohs(RFIFOW(fd,58)); - safestrncpy(server_name, (char*)RFIFOP(fd,60), 20); - type = RFIFOW(fd,82); - new_ = RFIFOW(fd,84); - RFIFOSKIP(fd,86); - - ShowInfo("Connection request of the char-server '%s' @ %u.%u.%u.%u:%u (account: '%s', pass: '%s', ip: '%s')\n", server_name, CONVIP(server_ip), server_port, sd->userid, sd->passwd, ip); - sprintf(message, "charserver - %s@%u.%u.%u.%u:%u", server_name, CONVIP(server_ip), server_port); - login_log(session[fd]->client_addr, sd->userid, 100, message); - - result = mmo_auth(sd, true); - if( runflag == LOGINSERVER_ST_RUNNING && - result == -1 && - sd->sex == 'S' && - sd->account_id >= 0 && sd->account_id < ARRAYLENGTH(ch_server) && - !session_isValid(ch_server[sd->account_id].fd) ) - { - ShowStatus("Connection of the char-server '%s' accepted.\n", server_name); - safestrncpy(ch_server[sd->account_id].name, server_name, sizeof(ch_server[sd->account_id].name)); - ch_server[sd->account_id].fd = fd; - ch_server[sd->account_id].ip = server_ip; - ch_server[sd->account_id].port = server_port; - ch_server[sd->account_id].users = 0; - ch_server[sd->account_id].type = type; - ch_server[sd->account_id].new_ = new_; - - session[fd]->func_parse = parse_fromchar; - session[fd]->flag.server = 1; - realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); - - // send connection success - WFIFOHEAD(fd,3); - WFIFOW(fd,0) = 0x2711; - WFIFOB(fd,2) = 0; - WFIFOSET(fd,3); - } - else - { - ShowNotice("Connection of the char-server '%s' REFUSED.\n", server_name); - WFIFOHEAD(fd,3); - WFIFOW(fd,0) = 0x2711; - WFIFOB(fd,2) = 3; - WFIFOSET(fd,3); - } - } - return 0; // processing will continue elsewhere - - default: - ShowNotice("Abnormal end of connection (ip: %s): Unknown packet 0x%x\n", ip, command); - set_eof(fd); - return 0; + subnet_count++; } } + if( subnet_count > 1 ) /* only useful if there is more than 1 available */ + ShowStatus("Read information about %d subnetworks.\n", subnet_count); + + fclose(fp); return 0; } - -void login_set_defaults() { - login_config.login_ip = INADDR_ANY; - login_config.login_port = 6900; - login_config.ipban_cleanup_interval = 60; - login_config.ip_sync_interval = 0; - login_config.log_login = true; - safestrncpy(login_config.date_format, "%Y-%m-%d %H:%M:%S", sizeof(login_config.date_format)); - login_config.console = false; - login_config.new_account_flag = true; - login_config.new_acc_length_limit = true; - login_config.use_md5_passwds = false; - login_config.group_id_to_connect = -1; - login_config.min_group_id_to_connect = -1; - login_config.check_client_version = false; - login_config.client_version_to_connect = 20; - - login_config.ipban = true; - login_config.dynamic_pass_failure_ban = true; - login_config.dynamic_pass_failure_ban_interval = 5; - login_config.dynamic_pass_failure_ban_limit = 7; - login_config.dynamic_pass_failure_ban_duration = 5; - login_config.use_dnsbl = false; - safestrncpy(login_config.dnsbl_servs, "", sizeof(login_config.dnsbl_servs)); - safestrncpy(login_config.account_engine, "auto", sizeof(login_config.account_engine)); - - login_config.client_hash_check = 0; - login_config.client_hash_nodes = NULL; - login_config.char_per_account = MAX_CHARS - MAX_CHAR_VIP - MAX_CHAR_BILLING; -#ifdef VIP_ENABLE - login_config.vip_sys.char_increase = MAX_CHAR_VIP; - login_config.vip_sys.group = 5; -#endif -} - -//----------------------------------- -// Reading main configuration file -//----------------------------------- -int login_config_read(const char* cfgName) -{ +/** + * Reading main configuration file. + * @param cfgName: Name of the configuration (could be fullpath) + * @return 0:success, 1:failure (file not found|readable) + */ +int login_config_read(const char* cfgName) { char line[1024], w1[1024], w2[1024]; FILE* fp = fopen(cfgName, "r"); if (fp == NULL) { @@ -1823,9 +646,9 @@ int login_config_read(const char* cfgName) else if(!strcmpi(w1, "console")) login_config.console = (bool)config_switch(w2); else if(!strcmpi(w1, "allowed_regs")) //account flood protection system - allowed_regs = atoi(w2); + login_config.allowed_regs = atoi(w2); else if(!strcmpi(w1, "time_allowed")) - time_allowed = atoi(w2); + login_config.time_allowed = atoi(w2); else if(!strcmpi(w1, "use_dnsbl")) login_config.use_dnsbl = (bool)config_switch(w2); else if(!strcmpi(w1, "dnsbl_servers")) @@ -1904,33 +727,61 @@ int login_config_read(const char* cfgName) return 0; } -/// Get the engine selected in the config settings. -/// Updates the config setting with the selected engine if 'auto'. -static AccountDB* get_account_engine(void) -{ - int i; - bool get_first = (strcmp(login_config.account_engine,"auto") == 0); +/** + * Init login-serv default configuration. + */ +void login_set_defaults() { + login_config.login_ip = INADDR_ANY; + login_config.login_port = 6900; + login_config.ipban_cleanup_interval = 60; + login_config.ip_sync_interval = 0; + login_config.log_login = true; + safestrncpy(login_config.date_format, "%Y-%m-%d %H:%M:%S", sizeof(login_config.date_format)); + login_config.console = false; + login_config.new_account_flag = true; + login_config.new_acc_length_limit = true; + login_config.use_md5_passwds = false; + login_config.group_id_to_connect = -1; + login_config.min_group_id_to_connect = -1; + login_config.check_client_version = false; + login_config.client_version_to_connect = date2version(PACKETVER); //20120410 => 30 + ShowInfo("loginconfig: client_version_to_connect = %d\n",login_config.client_version_to_connect); - for( i = 0; account_engines[i].constructor; ++i ) - { - char name[sizeof(login_config.account_engine)]; - AccountDB* db = account_engines[i].db; - if( db && db->get_property(db, "engine.name", name, sizeof(name)) && - (get_first || strcmp(name, login_config.account_engine) == 0) ) - { - if( get_first ) - safestrncpy(login_config.account_engine, name, sizeof(login_config.account_engine)); - return db; - } - } - return NULL; + login_config.ipban = true; + login_config.dynamic_pass_failure_ban = true; + login_config.dynamic_pass_failure_ban_interval = 5; + login_config.dynamic_pass_failure_ban_limit = 7; + login_config.dynamic_pass_failure_ban_duration = 5; + login_config.use_dnsbl = false; + safestrncpy(login_config.dnsbl_servs, "", sizeof(login_config.dnsbl_servs)); + safestrncpy(login_config.account_engine, "auto", sizeof(login_config.account_engine)); + login_config.allowed_regs = 1; + login_config.time_allowed = 10; //in second + + login_config.client_hash_check = 0; + login_config.client_hash_nodes = NULL; + login_config.char_per_account = MAX_CHARS - MAX_CHAR_VIP - MAX_CHAR_BILLING; +#ifdef VIP_ENABLE + login_config.vip_sys.char_increase = MAX_CHAR_VIP; + login_config.vip_sys.group = 5; +#endif + + //other default conf + safestrncpy(login_config.loginconf_name, "conf/login_athena.conf", sizeof(login_config.loginconf_name)); + safestrncpy(login_config.lanconf_name, "conf/subnet_athena.conf", sizeof(login_config.lanconf_name)); + safestrncpy(login_config.msgconf_name, "conf/msg_conf/login_msg.conf", sizeof(login_config.msgconf_name)); } -//-------------------------------------- -// Function called at exit of the server -//-------------------------------------- -void do_final(void) -{ + + + +/// Constructor destructor and signal handlers + +/** + * Login-serv destructor + * dealloc..., function called at exit of the login-serv + */ +void do_final(void) { int i; struct client_hash_node *hn = login_config.client_hash_nodes; @@ -1949,6 +800,8 @@ void do_final(void) do_final_msg(); ipban_final(); + do_final_loginclif(); + do_final_logincnslif(); for( i = 0; account_engines[i].constructor; ++i ) {// destroy all account engines @@ -1963,8 +816,7 @@ void do_final(void) online_db->destroy(online_db, NULL); auth_db->destroy(auth_db, NULL); - for( i = 0; i < ARRAYLENGTH(ch_server); ++i ) - chrif_server_destroy(i); + do_final_loginchrif(); if( login_fd != -1 ) { @@ -1975,42 +827,43 @@ void do_final(void) ShowStatus("Finished.\n"); } -//------------------------------ -// Function called when the server -// has received a crash signal. -//------------------------------ -void do_abort(void) -{ -} - -void set_server_type(void) -{ - SERVER_TYPE = ATHENA_SERVER_LOGIN; -} - - -/// Called when a terminate signal is received. -void do_shutdown(void) -{ - if( runflag != LOGINSERVER_ST_SHUTDOWN ) - { - int id; +/** + * Signal handler + * This function attempts to properly close the server when an interrupt signal is received. + * current signal catch : SIGTERM, SIGINT + */ +void do_shutdown(void) { + if( runflag != LOGINSERVER_ST_SHUTDOWN ) { runflag = LOGINSERVER_ST_SHUTDOWN; ShowStatus("Shutting down...\n"); // TODO proper shutdown procedure; kick all characters, wait for acks, ... [FlavioJS] - for( id = 0; id < ARRAYLENGTH(ch_server); ++id ) - chrif_server_reset(id); + do_shutdown_loginchrif(); flush_fifos(); runflag = CORE_ST_STOP; } } +/** + * Signal handler + * Function called when the server has received a crash signal. + * current signal catch : SIGSEGV, SIGFPE + */ +void do_abort(void) { +} -//------------------------------ -// Login server initialization -//------------------------------ -int do_init(int argc, char** argv) -{ +// Is this still used ?? +void set_server_type(void) { + SERVER_TYPE = ATHENA_SERVER_LOGIN; +} + +/** + * Login serv constructor + * Initialisation, function called at start of the login-serv. + * @param argc : number of argument from main() + * @param argv : arguments values from main() + * @return 0 everything ok else stopping programme execution. + */ +int do_init(int argc, char** argv) { int i; runflag = LOGINSERVER_ST_STARTING; @@ -2020,21 +873,17 @@ int do_init(int argc, char** argv) // read login-server configuration login_set_defaults(); + logcnslif_get_options(argc,argv); - LOGIN_CONF_NAME = "conf/login_athena.conf"; - LAN_CONF_NAME = "conf/subnet_athena.conf"; - MSG_CONF_NAME_EN = "conf/msg_conf/login_msg.conf"; - - cli_get_options(argc,argv); - - msg_config_read(MSG_CONF_NAME_EN); - login_config_read(LOGIN_CONF_NAME); - login_lan_config_read(LAN_CONF_NAME); + login_config_read(login_config.loginconf_name); + msg_config_read(login_config.msgconf_name); + login_lan_config_read(login_config.lanconf_name); + //end config rnd_init(); - for( i = 0; i < ARRAYLENGTH(ch_server); ++i ) - chrif_server_init(i); + do_init_loginclif(); + do_init_loginchrif(); // initialize logging if( login_config.log_login ) @@ -2045,23 +894,17 @@ int do_init(int argc, char** argv) // Online user database init online_db = idb_alloc(DB_OPT_RELEASE_DATA); - add_timer_func_list(waiting_disconnect_timer, "waiting_disconnect_timer"); + add_timer_func_list(login_waiting_disconnect_timer, "waiting_disconnect_timer"); // Interserver auth init auth_db = idb_alloc(DB_OPT_RELEASE_DATA); // set default parser as parse_login function - set_defaultparse(parse_login); + set_defaultparse(logclif_parse); // every 10 minutes cleanup online account db. - add_timer_func_list(online_data_cleanup, "online_data_cleanup"); - add_timer_interval(gettick() + 600*1000, online_data_cleanup, 0, 0, 600*1000); - - // add timer to detect ip address change and perform update - if (login_config.ip_sync_interval) { - add_timer_func_list(sync_ip_addresses, "sync_ip_addresses"); - add_timer_interval(gettick() + login_config.ip_sync_interval, sync_ip_addresses, 0, 0, login_config.ip_sync_interval); - } + add_timer_func_list(login_online_data_cleanup, "online_data_cleanup"); + add_timer_interval(gettick() + 600*1000, login_online_data_cleanup, 0, 0, 600*1000); // Account database init accounts = get_account_engine(); @@ -2087,41 +930,10 @@ int do_init(int argc, char** argv) runflag = LOGINSERVER_ST_RUNNING; } + do_init_logincnslif(); + ShowStatus("The login-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %u).\n\n", login_config.login_port); login_log(0, "login server", 100, "login server started"); - if( login_config.console ) { - add_timer_func_list(parse_console_timer, "parse_console_timer"); - add_timer_interval(gettick()+1000, parse_console_timer, 0, 0, 1000); //start in 1s each 1sec - } - return 0; } - -int login_msg_config_read(char *cfgName){ - return _msg_config_read(cfgName,LOGIN_MAX_MSG,msg_table); -} -const char* login_msg_txt(int msg_number){ - return _msg_txt(msg_number,LOGIN_MAX_MSG,msg_table); -} -void login_do_final_msg(void){ - _do_final_msg(LOGIN_MAX_MSG,msg_table); -} - -/*====================================================== - * Login-Server help option info - *------------------------------------------------------*/ -void display_helpscreen(bool do_exit) -{ - ShowInfo("Usage: %s [options]\n", SERVER_NAME); - ShowInfo("\n"); - ShowInfo("Options:\n"); - ShowInfo(" -?, -h [--help]\t\tDisplays this help screen.\n"); - ShowInfo(" -v [--version]\t\tDisplays the server's version.\n"); - ShowInfo(" --run-once\t\t\tCloses server after loading (testing).\n"); - ShowInfo(" --login-config \t\tAlternative login-server configuration.\n"); - ShowInfo(" --lan-config \t\tAlternative lag configuration.\n"); - ShowInfo(" --msg-config \t\tAlternative message configuration.\n"); - if( do_exit ) - exit(EXIT_SUCCESS); -} diff --git a/src/login/login.h b/src/login/login.h index 019218f3e6..612ba4c9f0 100644 --- a/src/login/login.h +++ b/src/login/login.h @@ -1,15 +1,22 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder +/** + * @file login.h + * Module purpose is to read configuration for login-server and handle accounts, + * and also to synchronise all login interfaces: loginchrif, loginclif, logincnslif. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams < r15k + * @author rAthena Dev Team + */ #ifndef _LOGIN_H_ #define _LOGIN_H_ #include "../common/mmo.h" // NAME_LENGTH,SEX_* #include "../common/core.h" // CORE_ST_LAST +#include "account.h" #include "../config/core.h" -enum E_LOGINSERVER_ST -{ +enum E_LOGINSERVER_ST { LOGINSERVER_ST_RUNNING = CORE_ST_LAST, LOGINSERVER_ST_STARTING, LOGINSERVER_ST_SHUTDOWN, @@ -27,7 +34,7 @@ struct login_session_data { char sex; /// 'F','M','S' char userid[NAME_LENGTH]; /// account name - char passwd[32+1]; /// 23+1 for plaintext, 32+1 for md5-ed passwords + char passwd[PASSWD_LENGTH]; // 23+1 for plaintext, 32+1 for md5-ed passwords int passwdenc; /// was the passwd transmited encrypted or clear ? char md5key[20]; /// md5 key of session (each connection could be encrypted with a md5 key) uint16 md5keylen; /// len of the md5 key @@ -43,6 +50,7 @@ struct login_session_data { int fd; ///socket of client }; +#define MAX_SERVERS 30 //max number of mapserv that could be attach ///Struct describing 1 char-serv attach to us struct mmo_char_server { char name[20]; ///char-serv name @@ -53,9 +61,10 @@ struct mmo_char_server { uint16 type; /// 0=normal, 1=maintenance, 2=over 18, 3=paying, 4=P2P uint16 new_; /// should display as 'new'? }; +extern struct mmo_char_server ch_server[MAX_SERVERS]; struct client_hash_node { - unsigned int group_id; ///group + unsigned int group_id; //inferior or egal group to apply restriction uint8 hash[16]; ///hash required for that groupid or below struct client_hash_node *next; ///next entry }; @@ -84,10 +93,16 @@ struct Login_Config { bool use_dnsbl; /// dns blacklist blocking ? char dnsbl_servs[1024]; /// comma-separated list of dnsbl servers - char account_engine[256]; /// name of the engine to use (defaults to auto, for the first available engine) + char account_engine[256]; // name of the engine to use (defaults to auto, for the first available engine) + int allowed_regs; //max number of registration + int time_allowed; //registration intervall in seconds - int client_hash_check; /// flags for checking client md5 - struct client_hash_node *client_hash_nodes; /// linked list containg md5 hash for each gm group + int client_hash_check; // flags for checking client md5 + struct client_hash_node *client_hash_nodes; // linked list containg md5 hash for each gm group + char loginconf_name[256]; //name of main config file + char msgconf_name[256]; //name of msg_conf config file + char lanconf_name[256]; //name of lan config file + int char_per_account; /// number of characters an account can have #ifdef VIP_ENABLE struct { @@ -96,6 +111,7 @@ struct Login_Config { } vip_sys; #endif }; +extern struct Login_Config login_config; #define sex_num2str(num) ( (num == SEX_FEMALE ) ? 'F' : (num == SEX_MALE ) ? 'M' : 'S' ) #define sex_str2num(str) ( (str == 'F' ) ? SEX_FEMALE : (str == 'M' ) ? SEX_MALE : SEX_SERVER ) @@ -103,15 +119,119 @@ struct Login_Config { #define msg_config_read(cfgName) login_msg_config_read(cfgName) #define msg_txt(msg_number) login_msg_txt(msg_number) #define do_final_msg() login_do_final_msg() - int login_msg_config_read(char *cfgName); const char* login_msg_txt(int msg_number); void login_do_final_msg(void); +/// Online User Database [Wizputer] +struct online_login_data { + int account_id; + int waiting_disconnect; + int char_server; +}; +extern DBMap* online_db; // int account_id -> struct online_login_data* -#define MAX_SERVERS 30 ///number of charserv loginserv can handle -extern struct mmo_char_server ch_server[MAX_SERVERS]; ///array of char-servs data -extern struct Login_Config login_config; ///config of login serv +/// Auth database +#define AUTH_TIMEOUT 30000 +struct auth_node { + int account_id; + uint32 login_id1; + uint32 login_id2; + uint32 ip; + char sex; + uint32 version; + uint8 clienttype; +}; +extern DBMap* auth_db; // int account_id -> struct auth_node* +///Accessors +AccountDB* login_get_accounts_db(void); + +/** + * Sub function to create an online_login_data and save it to db. + * @param key: Key of the database entry + * @param ap: args + * @return : Data identified by the key to be put in the database + * @see DBCreateData + */ +DBData login_create_online_user(DBKey key, va_list args); + +/** + * Function to add a user in online_db. + * Checking if the user is already registered in the db. + * Stop disconnection timer if set. + * @param char_server: id of char-serv on wich the player is + * @param account_id: the account identifier + * @return the new|registered online data + */ +struct online_login_data* login_add_online_user(int char_server, int account_id); + +/** + * Function to remove a user from online_db. + * Checking if user was already scheduled for deletion, and remove that timer if found. + * @param account_id: the account identifier + */ +void login_remove_online_user(int account_id); + +/** + * Timered function to disconnect a user from login. + * This is done either after auth_ok or kicked by char-server. + * Removing user from auth_db and online_db. + * Delay is AUTH_TIMEOUT by default. + * @param tid: timer id + * @param tick: tick of execution + * @param id: user account id + * @param data: unused + * @return :0 + */ +int login_waiting_disconnect_timer(int tid, unsigned int tick, int id, intptr_t data); + +/** + * Sub function to apply on online_db. + * Mark a character as offline. + * @param data: 1 entry in the db + * @param ap: args + * @return : Value to be added up by the function that is applying this + * @see DBApply + */ +int login_online_db_setoffline(DBKey key, DBData *data, va_list ap); + +/** + * Test to determine if an IP come from LAN or WAN. + * @param ip: ip to check if in auth network + * @return 0 if from wan, or subnet_char_ip if lan + */ +int lan_subnetcheck(uint32 ip); + + +/** + * Create a new account and save it in db/sql. + * @param userid: string for user login + * @param pass: string for user pass + * @param sex: should be M|F|S (todo make an enum ?) + * @param last_ip: + * @return : + * -1: success + * 0: unregistered id (wrong sex fail to create in db); + * 1: incorrect pass or userid (userid|pass too short or already exist); + * 3: registration limit exceeded; + */ +int login_mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip); + +/** + * Check/authentication of a connection. + * @param sd: string (atm:md5key or dbpass) + * @param isServer: string (atm:md5key or dbpass) + * @return : + * -1: success + * 0: unregistered id; + * 1: incorrect pass; + * 2: expired id + * 3: blacklisted (or registration limit exceeded if new acc); + * 5: invalid client_version|hash; + * 6: banned + * x: acc state (TODO document me deeper) + */ +int login_mmo_auth(struct login_session_data* sd, bool isServer); #endif /* _LOGIN_H_ */ diff --git a/src/login/loginchrif.c b/src/login/loginchrif.c new file mode 100644 index 0000000000..fa5d18c1be --- /dev/null +++ b/src/login/loginchrif.c @@ -0,0 +1,967 @@ +/** + * @file loginchrif.c + * Module purpose is to handle incoming and outgoing requests with char-server. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams originally in login.c + * @author rAthena Dev Team + */ + +#include "../common/timer.h" //difftick +#include "../common/strlib.h" //safeprint +#include "../common/showmsg.h" //show notice +#include "../common/socket.h" //wfifo session +#include "../common/malloc.h" +#include "account.h" +#include "ipban.h" //ipban_check +#include "login.h" +#include "loginlog.h" +#include "loginclif.h" +#include "loginchrif.h" + +#include +#include +#include + +//early declaration +void logchrif_on_disconnect(int id); + +/** + * Packet send to all char-servers, except one. (wos: without our self) + * @param sfd: fd to discard sending to + * @param buf: packet to send in form of an array buffer + * @param len: size of packet + * @return : the number of char-serv the packet was sent to + */ +int logchrif_sendallwos(int sfd, uint8* buf, size_t len) { + int i, c; + for( i = 0, c = 0; i < ARRAYLENGTH(ch_server); ++i ) { + int fd = ch_server[i].fd; + if( session_isValid(fd) && fd != sfd ){ + WFIFOHEAD(fd,len); + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + ++c; + } + } + return c; +} + +/** + * Timered function to synchronize ip addresses. + * Requesting all char to update their registered ip and transmit their new ip. + * Performed each ip_sync_interval. + * @param tid: timer id + * @param tick: tick of execution + * @param id: unused + * @param data: unused + * @return 0 + */ +static int logchrif_sync_ip_addresses(int tid, unsigned int tick, int id, intptr_t data) { + uint8 buf[2]; + ShowInfo("IP Sync in progress...\n"); + WBUFW(buf,0) = 0x2735; + logchrif_sendallwos(-1, buf, 2); + return 0; +} + + + + +/// Parsing handlers + +/** + * Request from char-server to authenticate an account. + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @param ip: char-serv ip (used for info) + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_reqauth(int fd, int id,char* ip){ + if( RFIFOREST(fd) < 23 ) + return 0; + else{ + struct auth_node* node; + int account_id = RFIFOL(fd,2); + uint32 login_id1 = RFIFOL(fd,6); + uint32 login_id2 = RFIFOL(fd,10); + uint8 sex = RFIFOB(fd,14); + //uint32 ip_ = ntohl(RFIFOL(fd,15)); + int request_id = RFIFOL(fd,19); + RFIFOSKIP(fd,23); + + node = (struct auth_node*)idb_get(auth_db, account_id); + if( runflag == LOGINSERVER_ST_RUNNING && + node != NULL && + node->account_id == account_id && + node->login_id1 == login_id1 && + node->login_id2 == login_id2 && + node->sex == sex_num2str(sex) /*&& + node->ip == ip_*/ ){// found + //ShowStatus("Char-server '%s': authentication of the account %d accepted (ip: %s).\n", server[id].name, account_id, ip); + + // send ack + WFIFOHEAD(fd,25); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = account_id; + WFIFOL(fd,6) = login_id1; + WFIFOL(fd,10) = login_id2; + WFIFOB(fd,14) = sex; + WFIFOB(fd,15) = 0;// ok + WFIFOL(fd,16) = request_id; + WFIFOL(fd,20) = node->version; + WFIFOB(fd,24) = node->clienttype; + WFIFOSET(fd,25); + + // each auth entry can only be used once + idb_remove(auth_db, account_id); + }else{// authentication not found + ShowStatus("Char-server '%s': authentication of the account %d REFUSED (ip: %s).\n", ch_server[id].name, account_id, ip); + WFIFOHEAD(fd,25); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = account_id; + WFIFOL(fd,6) = login_id1; + WFIFOL(fd,10) = login_id2; + WFIFOB(fd,14) = sex; + WFIFOB(fd,15) = 1;// auth failed + WFIFOL(fd,16) = request_id; + WFIFOL(fd,20) = 0; + WFIFOB(fd,24) = 0; + WFIFOSET(fd,25); + } + } + return 1; +} + +/** + * Receive a request to update user count for char-server identified by id. + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_ackusercount(int fd, int id){ + if( RFIFOREST(fd) < 6 ) + return 0; + else{ + int users = RFIFOL(fd,2); + RFIFOSKIP(fd,6); + // how many users on world? (update) + if( ch_server[id].users != users ){ + ShowStatus("set users %s : %d\n", ch_server[id].name, users); + ch_server[id].users = users; + } + } + return 1; +} + +/** + * Receive a request from char-server to change e-mail from default "a@a.com". + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @param ip: char-serv ip (used for info) + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_updmail(int fd, int id, char* ip){ + if (RFIFOREST(fd) < 46) + return 0; + else{ + AccountDB* accounts = login_get_accounts_db(); + struct mmo_account acc; + char email[40]; + + int account_id = RFIFOL(fd,2); + safestrncpy(email, (char*)RFIFOP(fd,6), 40); remove_control_chars(email); + RFIFOSKIP(fd,46); + + if( e_mail_check(email) == 0 ) + ShowNotice("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - e-mail is invalid (account: %d, ip: %s)\n", ch_server[id].name, account_id, ip); + else if( !accounts->load_num(accounts, &acc, account_id) || strcmp(acc.email, "a@a.com") == 0 || acc.email[0] == '\0' ) + ShowNotice("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - account doesn't exist or e-mail of account isn't default e-mail (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip); + else{ + memcpy(acc.email, email, 40); + ShowNotice("Char-server '%s': Create an e-mail on an account with a default e-mail (account: %d, new e-mail: %s, ip: %s).\n", ch_server[id].name, account_id, email, ip); + // Save + accounts->save(accounts, &acc); + } + } + return 1; +} + +/** + * Transmit account data to char_server + * S 2717 aid.W email.40B exp_time.L group_id.B char_slot.B birthdate.11B pincode.5B pincode_change.L bank_vault.L + * isvip.1B char_vip.1B max_billing.1B (tot 79) + * @return -1 : account not found, 1:sucess + */ +int logchrif_send_accdata(int fd, uint32 aid) { + struct mmo_account acc; + time_t expiration_time = 0; + char email[40] = ""; + int group_id = 0; + char birthdate[10+1] = ""; + char pincode[PINCODE_LENGTH+1]; + int bank_vault = 0; + char isvip = false; + uint8 char_slots = MIN_CHARS, char_vip = 0; + + memset(pincode,0,PINCODE_LENGTH+1); + AccountDB* accounts = login_get_accounts_db(); + if( !accounts->load_num(accounts, &acc, aid) ) + return -1; + else { + safestrncpy(email, acc.email, sizeof(email)); + expiration_time = acc.expiration_time; + group_id = acc.group_id; + + safestrncpy(birthdate, acc.birthdate, sizeof(birthdate)); + safestrncpy(pincode, acc.pincode, sizeof(pincode)); + bank_vault = acc.bank_vault; +#ifdef VIP_ENABLE + char_vip = login_config.vip_sys.char_increase; + if( acc.vip_time > time(NULL) ) { + isvip = true; + char_slots = login_config.char_per_account + char_vip; + } else + char_slots = login_config.char_per_account; +#endif + } + + WFIFOHEAD(fd,79); + WFIFOW(fd,0) = 0x2717; + WFIFOL(fd,2) = aid; + safestrncpy((char*)WFIFOP(fd,6), email, 40); + WFIFOL(fd,46) = (uint32)expiration_time; + WFIFOB(fd,50) = (unsigned char)group_id; + WFIFOB(fd,51) = char_slots; + safestrncpy((char*)WFIFOP(fd,52), birthdate, 10+1); + safestrncpy((char*)WFIFOP(fd,63), pincode, 4+1 ); + WFIFOL(fd,68) = (uint32)acc.pincode_change; + WFIFOL(fd,72) = bank_vault; + WFIFOB(fd,76) = isvip; + WFIFOB(fd,77) = char_vip; + WFIFOB(fd,78) = MAX_CHAR_BILLING; //TODO create a config for this + WFIFOSET(fd,79); + return 1; +} + +/** + * Transmit vip specific data to char-serv (will be transfered to mapserv) + */ +int logchrif_sendvipdata(int fd, struct mmo_account acc, char isvip, char isgm, int mapfd) { +#ifdef VIP_ENABLE + WFIFOHEAD(fd,19); + WFIFOW(fd,0) = 0x2743; + WFIFOL(fd,2) = acc.account_id; + WFIFOL(fd,6) = (int)acc.vip_time; + WFIFOB(fd,10) = isvip; + WFIFOL(fd,11) = acc.group_id; //new group id + WFIFOL(fd,15) = isgm; + WFIFOL(fd,16) = mapfd; //link to mapserv + WFIFOSET(fd,20); + chrif_send_accdata(fd,acc.account_id); //refresh char with new setting +#endif + return 1; +} + +/** + * Receive a request for account data reply by sending all mmo_account information. + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @param ip: char-serv ip (used for info) + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_reqaccdata(int fd, int id, char *ip){ + if( RFIFOREST(fd) < 6 ) + return 0; + else { + uint32 aid = RFIFOL(fd,2); + RFIFOSKIP(fd,6); + if( logchrif_send_accdata(fd,aid) < 0 ) + ShowNotice("Char-server '%s': account %d NOT found (ip: %s).\n", ch_server[id].name, aid, ip); + } + return 1; +} + +/** + * Ping request from char-server to send a reply. + * @param fd: fd to parse from (char-serv) + * @return 1 success + */ +int logchrif_parse_keepalive(int fd){ + RFIFOSKIP(fd,2); + WFIFOHEAD(fd,2); + WFIFOW(fd,0) = 0x2718; + WFIFOSET(fd,2); + return 1; +} + +/** + * Map server send information to change an email of an account via char-server. + * 0x2722 .L .40B .40B + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @param ip: char-serv ip (used for info) + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_reqchangemail(int fd, int id, char* ip){ + if (RFIFOREST(fd) < 86) + return 0; + else{ + struct mmo_account acc; + AccountDB* accounts = login_get_accounts_db(); + char actual_email[40]; + char new_email[40]; + + int account_id = RFIFOL(fd,2); + safestrncpy(actual_email, (char*)RFIFOP(fd,6), 40); + safestrncpy(new_email, (char*)RFIFOP(fd,46), 40); + RFIFOSKIP(fd, 86); + + if( e_mail_check(actual_email) == 0 ) + ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual email is invalid (account: %d, ip: %s)\n", ch_server[id].name, account_id, ip); + else if( e_mail_check(new_email) == 0 ) + ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a invalid new e-mail (account: %d, ip: %s)\n", ch_server[id].name, account_id, ip); + else if( strcmpi(new_email, "a@a.com") == 0 ) + ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a default e-mail (account: %d, ip: %s)\n", ch_server[id].name, account_id, ip); + else if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but account doesn't exist (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip); + else if( strcmpi(acc.email, actual_email) != 0 ) + ShowNotice("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual e-mail is incorrect (account: %d (%s), actual e-mail: %s, proposed e-mail: %s, ip: %s).\n", ch_server[id].name, account_id, acc.userid, acc.email, actual_email, ip); + else{ + safestrncpy(acc.email, new_email, 40); + ShowNotice("Char-server '%s': Modify an e-mail on an account (@email GM command) (account: %d (%s), new e-mail: %s, ip: %s).\n", ch_server[id].name, account_id, acc.userid, new_email, ip); + // Save + accounts->save(accounts, &acc); + } + } + return 1; +} + +/** + * Receiving an account state update request from a map-server (relayed via char-server). + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @param ip: char-serv ip (used for info) + * @return 0 not enough info transmitted, 1 success + * TODO seems pretty damn close to logchrif_parse_reqbanacc + */ +int logchrif_parse_requpdaccstate(int fd, int id, char* ip){ + if (RFIFOREST(fd) < 10) + return 0; + else{ + struct mmo_account acc; + + int account_id = RFIFOL(fd,2); + unsigned int state = RFIFOL(fd,6); + RFIFOSKIP(fd,10); + + AccountDB* accounts = login_get_accounts_db(); + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Error of Status change (account: %d not found, suggested status %d, ip: %s).\n", ch_server[id].name, account_id, state, ip); + else if( acc.state == state ) + ShowNotice("Char-server '%s': Error of Status change - actual status is already the good status (account: %d, status %d, ip: %s).\n", ch_server[id].name, account_id, state, ip); + else{ + ShowNotice("Char-server '%s': Status change (account: %d, new status %d, ip: %s).\n", ch_server[id].name, account_id, state, ip); + + acc.state = state; + // Save + accounts->save(accounts, &acc); + + // notify other servers + if (state != 0){ + uint8 buf[11]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = account_id; + WBUFB(buf,6) = 0; // 0: change of state, 1: ban + WBUFL(buf,7) = state; // status or final date of a banishment + logchrif_sendallwos(-1, buf, 11); + } + } + } + return 1; +} + +/** + * Receiving a ban request from map-server via char-server. + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @param ip: char-serv ip (used for info) + * @return 0 not enough info transmitted, 1 success + * TODO check logchrif_parse_requpdaccstate for possible merge + */ +int logchrif_parse_reqbanacc(int fd, int id, char* ip){ + if (RFIFOREST(fd) < 10) + return 0; + else{ + struct mmo_account acc; + AccountDB* accounts = login_get_accounts_db(); + + int account_id = RFIFOL(fd,2); + int timediff = RFIFOL(fd,6); + RFIFOSKIP(fd,10); + + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Error of ban request (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip); + else{ + time_t timestamp; + if (acc.unban_time == 0 || acc.unban_time < time(NULL)) + timestamp = time(NULL); // new ban + else + timestamp = acc.unban_time; // add to existing ban + timestamp += timediff; + if (timestamp == -1) + ShowNotice("Char-server '%s': Error of ban request (account: %d, invalid date, ip: %s).\n", ch_server[id].name, account_id, ip); + else if( timestamp <= time(NULL) || timestamp == 0 ) + ShowNotice("Char-server '%s': Error of ban request (account: %d, new date unbans the account, ip: %s).\n", ch_server[id].name, account_id, ip); + else{ + uint8 buf[11]; + char tmpstr[24]; + timestamp2string(tmpstr, sizeof(tmpstr), timestamp, login_config.date_format); + ShowNotice("Char-server '%s': Ban request (account: %d, new final date of banishment: %d (%s), ip: %s).\n", ch_server[id].name, account_id, timestamp, tmpstr, ip); + + acc.unban_time = timestamp; + + // Save + accounts->save(accounts, &acc); + + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = account_id; + WBUFB(buf,6) = 1; // 0: change of status, 1: ban + WBUFL(buf,7) = (uint32)timestamp; // status or final date of a banishment + logchrif_sendallwos(-1, buf, 11); + } + } + } + return 1; +} + +/** + * Receiving a sex change request (sex is reversed). + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @param ip: char-serv ip (used for info) + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_reqchgsex(int fd, int id, char* ip){ + if( RFIFOREST(fd) < 6 ) + return 0; + else{ + struct mmo_account acc; + AccountDB* accounts = login_get_accounts_db(); + + int account_id = RFIFOL(fd,2); + RFIFOSKIP(fd,6); + + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Error of sex change (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip); + else if( acc.sex == 'S' ) + ShowNotice("Char-server '%s': Error of sex change - account to change is a Server account (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip); + else{ + unsigned char buf[7]; + char sex = ( acc.sex == 'M' ) ? 'F' : 'M'; //Change gender + + ShowNotice("Char-server '%s': Sex change (account: %d, new sex %c, ip: %s).\n", ch_server[id].name, account_id, sex, ip); + + acc.sex = sex; + // Save + accounts->save(accounts, &acc); + + // announce to other servers + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = account_id; + WBUFB(buf,6) = sex_str2num(sex); + logchrif_sendallwos(-1, buf, 7); + } + } + return 1; +} + +/** + * We receive account_reg2 from a char-server, and we send them to other char-servers. + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @param ip: char-serv ip (used for info) + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_updreg2(int fd, int id, char* ip){ + int j; + if( RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2) ) + return 0; + else{ + struct mmo_account acc; + AccountDB* accounts = login_get_accounts_db(); + int account_id = RFIFOL(fd,4); + + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowStatus("Char-server '%s': receiving (from the char-server) of account_reg2 (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip); + else{ + int len; + int p; + ShowNotice("char-server '%s': receiving (from the char-server) of account_reg2 (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip); + for( j = 0, p = 13; j < ACCOUNT_REG2_NUM && p < RFIFOW(fd,2); ++j ){ + sscanf((char*)RFIFOP(fd,p), "%31c%n", acc.account_reg2[j].str, &len); + acc.account_reg2[j].str[len]='\0'; + p +=len+1; //+1 to skip the '\0' between strings. + sscanf((char*)RFIFOP(fd,p), "%255c%n", acc.account_reg2[j].value, &len); + acc.account_reg2[j].value[len]='\0'; + p +=len+1; + remove_control_chars(acc.account_reg2[j].str); + remove_control_chars(acc.account_reg2[j].value); + } + acc.account_reg2_num = j; + // Save + accounts->save(accounts, &acc); + // Sending information towards the other char-servers. + RFIFOW(fd,0) = 0x2729;// reusing read buffer + logchrif_sendallwos(fd, RFIFOP(fd,0), RFIFOW(fd,2)); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + } + return 1; +} + +/** + * Receiving an unban request from map-server via char-server. + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @param ip: char-serv ip (used for info) + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_requnbanacc(int fd, int id, char* ip){ + if( RFIFOREST(fd) < 6 ) + return 0; + else{ + struct mmo_account acc; + AccountDB* accounts = login_get_accounts_db(); + + int account_id = RFIFOL(fd,2); + RFIFOSKIP(fd,6); + + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Error of UnBan request (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip); + else if( acc.unban_time == 0 ) + ShowNotice("Char-server '%s': Error of UnBan request (account: %d, no change for unban date, ip: %s).\n", ch_server[id].name, account_id, ip); + else{ + ShowNotice("Char-server '%s': UnBan request (account: %d, ip: %s).\n", ch_server[id].name, account_id, ip); + acc.unban_time = 0; + accounts->save(accounts, &acc); + } + } + return 1; +} + +/** + * Set account_id to online. + * @author [Wizputer] + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_setacconline(int fd, int id){ + if( RFIFOREST(fd) < 6 ) + return 0; + login_add_online_user(id, RFIFOL(fd,2)); + RFIFOSKIP(fd,6); + return 1; +} + +/** + * Set account_id to offline. + * @author [Wizputer] + * @param fd: fd to parse from (char-serv) + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_setaccoffline(int fd){ + if( RFIFOREST(fd) < 6 ) + return 0; + login_remove_online_user(RFIFOL(fd,2)); + RFIFOSKIP(fd,6); + return 1; +} + +/** + * Receive list of all online accounts. + * @author [Skotlex] + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_updonlinedb(int fd, int id){ + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + else{ + uint32 i, users; + online_db->foreach(online_db, login_online_db_setoffline, id); //Set all chars from this char-server offline first + users = RFIFOW(fd,4); + for (i = 0; i < users; i++) { + int aid = RFIFOL(fd,6+i*4); + struct online_login_data *p = idb_ensure(online_db, aid, login_create_online_user); + p->char_server = id; + if (p->waiting_disconnect != INVALID_TIMER){ + delete_timer(p->waiting_disconnect, login_waiting_disconnect_timer); + p->waiting_disconnect = INVALID_TIMER; + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + } + return 1; +} + +/** + * Request account_reg2 for a character. + * @param fd: fd to parse from (char-serv) + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_reqacc2reg(int fd){ + int j; + if (RFIFOREST(fd) < 10) + return 0; + else{ + struct mmo_account acc; + AccountDB* accounts = login_get_accounts_db(); + size_t off; + + int account_id = RFIFOL(fd,2); + int char_id = RFIFOL(fd,6); + RFIFOSKIP(fd,10); + + WFIFOHEAD(fd,ACCOUNT_REG2_NUM*sizeof(struct global_reg)); + WFIFOW(fd,0) = 0x2729; + WFIFOL(fd,4) = account_id; + WFIFOL(fd,8) = char_id; + WFIFOB(fd,12) = 1; //Type 1 for Account2 registry + + off = 13; + if( accounts->load_num(accounts, &acc, account_id) ){ + for( j = 0; j < acc.account_reg2_num; j++ ){ + if( acc.account_reg2[j].str[0] != '\0' ){ + off += sprintf((char*)WFIFOP(fd,off), "%s", acc.account_reg2[j].str)+1; //We add 1 to consider the '\0' in place. + off += sprintf((char*)WFIFOP(fd,off), "%s", acc.account_reg2[j].value)+1; + } + } + } + + WFIFOW(fd,2) = (uint16)off; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 1; +} + +/** + * Received new charip from char-serv, update information. + * @param fd: char-serv file descriptor + * @param id: char-serv id + * @return 0 not enough info transmitted, 1 success + */ +int logchrif_parse_updcharip(int fd, int id){ + if( RFIFOREST(fd) < 6 ) + return 0; + ch_server[id].ip = ntohl(RFIFOL(fd,2)); + ShowInfo("Updated IP of Server #%d to %d.%d.%d.%d.\n",id, CONVIP(ch_server[id].ip)); + RFIFOSKIP(fd,6); + return 1; +} + +/** + * Request to set all accounts offline. + * @param fd: fd to parse from (char-serv) + * @param id: id of char-serv (char-serv) + * @return 1 success + */ +int logchrif_parse_setalloffline(int fd, int id){ + ShowInfo("Setting accounts from char-server %d offline.\n", id); + online_db->foreach(online_db, login_online_db_setoffline, id); + RFIFOSKIP(fd,2); + return 1; +} + +/** + * Request to change PIN Code for an account. + * @param fd: fd to parse from (char-serv) + * @return 0 fail (packet does not have enough data), 1 success + */ +int logchrif_parse_updpincode(int fd){ + if( RFIFOREST(fd) < 11 ) + return 0; + else{ + struct mmo_account acc; + AccountDB* accounts = login_get_accounts_db(); + + if( accounts->load_num(accounts, &acc, RFIFOL(fd,2) ) ){ + strncpy( acc.pincode, (char*)RFIFOP(fd,6), 5 ); + acc.pincode_change = time( NULL ); + accounts->save(accounts, &acc); + } + RFIFOSKIP(fd,11); + } + return 1; +} + +/** + * PIN Code was incorrectly entered too many times. + * @param fd: fd to parse from (char-serv) + * @return 0 fail (packet does not have enough data), 1 success (continue parsing) + */ +int logchrif_parse_pincode_authfail(int fd){ + if( RFIFOREST(fd) < 6 ) + return 0; + else{ + struct mmo_account acc; + AccountDB* accounts = login_get_accounts_db(); + if( accounts->load_num(accounts, &acc, RFIFOL(fd,2) ) ){ + struct online_login_data* ld; + + ld = (struct online_login_data*)idb_get(online_db,acc.account_id); + + if( ld == NULL ) + return 0; + + login_log( host2ip(acc.last_ip), acc.userid, 100, "PIN Code check failed" ); + } + login_remove_online_user(acc.account_id); + RFIFOSKIP(fd,6); + } + return 1; +} + +/** + * Request the bank info of login + * @param fd: fd to parse from (char-serv) + * @param id: char serv id + * @param ip: char-serv ip (used for info) + * @return 0 fail (packet does not have enough data), 1 success (continue parsing) + */ +int logchrif_parse_bankvault(int fd, int id, char* ip){ + if( RFIFOREST(fd) < 11 ) + return 0; + else { + struct mmo_account acc; + + + int account_id = RFIFOL(fd,2); + char type = RFIFOB(fd,6); + int32 data = RFIFOL(fd,7); + RFIFOSKIP(fd,11); + + AccountDB* accounts = login_get_accounts_db(); + if( !accounts->load_num(accounts, &acc, account_id) ) + ShowNotice("Char-server '%s': Error on banking (account: %d not found, ip: %s).\n", ch_server[id].name, account_id, ip); + else{ + unsigned char buf[12]; + if(type==2){ // upd and Save + acc.bank_vault = data; + accounts->save(accounts, &acc); + WBUFB(buf,10) = 1; + } else { + WBUFB(buf,10) = 0; + } + // announce to other servers + WBUFW(buf,0) = 0x2741; + WBUFL(buf,2) = account_id; + WBUFL(buf,6) = acc.bank_vault; + logchrif_sendallwos(-1, buf, 11); + } + } + return 1; +} + +/** + * Received a vip data reqest from char + * type is the query to perform + * &1 : Select info and update old_groupid + * &2 : Update vip time + * @param fd link to charserv + * @return 0 missing data, 1 succeeded + */ +int logchrif_parse_reqvipdata(int fd) { +#ifdef VIP_ENABLE + if( RFIFOREST(fd) < 15 ) + return 0; + else { //request vip info + struct mmo_account acc; + int aid = RFIFOL(fd,2); + int8 type = RFIFOB(fd,6); + int32 timediff = RFIFOL(fd,7); + int mapfd = RFIFOL(fd,11); + RFIFOSKIP(fd,15); + + if( accounts->load_num(accounts, &acc, aid ) ) { + time_t now = time(NULL); + time_t vip_time = acc.vip_time; + bool isvip = false; + + if( acc.group_id > login_config.vip_sys.group ) { //Don't change group if it's higher. + chrif_sendvipdata(fd,acc,false,true,mapfd); + return 1; + } + if( type&2 ) { + if(!vip_time) vip_time = now; //new entry + vip_time += timediff; // set new duration + } + if( now < vip_time ) { //isvip + if(acc.group_id != login_config.vip_sys.group) //only upd this if we're not vip already + acc.old_group = acc.group_id; + acc.group_id = login_config.vip_sys.group; + acc.char_slots = login_config.char_per_account + login_config.vip_sys.char_increase; + isvip = true; + } else { //expired or @vip -xx + vip_time = 0; + if(acc.group_id == login_config.vip_sys.group) //prevent alteration in case account wasn't registered as vip yet + acc.group_id = acc.old_group; + acc.old_group = 0; + acc.char_slots = login_config.char_per_account; + } + acc.vip_time = vip_time; + accounts->save(accounts,&acc); + if( type&1 ) chrif_sendvipdata(fd,acc,isvip,false,mapfd); + } + } +#endif + return 1; +} + +/** + * Entry point from char-server to log-server. + * Function that checks incoming command, then splits it to the correct handler. + * @param fd: file descriptor to parse, (link to char-serv) + * @return 0=invalid server,marked for disconnection,unknow packet; 1=success + */ +int logchrif_parse(int fd){ + int cid; //char-serv id + uint32 ipl; + char ip[16]; + + ARR_FIND( 0, ARRAYLENGTH(ch_server), cid, ch_server[cid].fd == fd ); + if( cid == ARRAYLENGTH(ch_server) ){// not a char server + ShowDebug("logchrif_parse: Disconnecting invalid session #%d (is not a char-server)\n", fd); + set_eof(fd); + do_close(fd); + return 0; + } + + if( session[fd]->flag.eof ){ + do_close(fd); + ch_server[cid].fd = -1; + logchrif_on_disconnect(cid); + return 0; + } + + ipl = ch_server[cid].ip; + ip2str(ipl, ip); + + while( RFIFOREST(fd) >= 2 ){ + int next = 1; + uint16 command = RFIFOW(fd,0); + switch( command ){ + case 0x2712: next = logchrif_parse_reqauth(fd, cid, ip); break; + case 0x2714: next = logchrif_parse_ackusercount(fd, cid); break; + case 0x2715: next = logchrif_parse_updmail(fd, cid, ip); break; + case 0x2716: next = logchrif_parse_reqaccdata(fd, cid, ip); break; + case 0x2719: next = logchrif_parse_keepalive(fd); break; + case 0x2722: next = logchrif_parse_reqchangemail(fd,cid,ip); break; + case 0x2724: next = logchrif_parse_requpdaccstate(fd,cid,ip); break; + case 0x2725: next = logchrif_parse_reqbanacc(fd,cid,ip); break; + case 0x2727: next = logchrif_parse_reqchgsex(fd,cid,ip); break; + case 0x2728: next = logchrif_parse_updreg2(fd,cid,ip); break; + case 0x272a: next = logchrif_parse_requnbanacc(fd,cid,ip); break; + case 0x272b: next = logchrif_parse_setacconline(fd,cid); break; + case 0x272c: next = logchrif_parse_setaccoffline(fd); break; + case 0x272d: next = logchrif_parse_updonlinedb(fd,cid); break; + case 0x272e: next = logchrif_parse_reqacc2reg(fd); break; + case 0x2736: next = logchrif_parse_updcharip(fd,cid); break; + case 0x2737: next = logchrif_parse_setalloffline(fd,cid); break; + case 0x2738: next = logchrif_parse_updpincode(fd); break; + case 0x2739: next = logchrif_parse_pincode_authfail(fd); break; + case 0x2740: next = logchrif_parse_bankvault(fd,cid,ip); break; + case 0x2742: next = logchrif_parse_reqvipdata(fd); break; //Vip sys + default: + ShowError("logchrif_parse: Unknown packet 0x%x from a char-server! Disconnecting!\n", command); + set_eof(fd); + return 0; + } // switch + if(next==0) return 0; // avoid processing of followup packets (prev was probably incomplete) + } // while + return 1; //or 0 +} + + + + +/// Constructor destructor and signal handlers + +/** + * Initializes a server structure. + * @param id: id of char-serv (should be >0, FIXME) + */ +void logchrif_server_init(int id) { + memset(&ch_server[id], 0, sizeof(ch_server[id])); + ch_server[id].fd = -1; +} + +/** + * Destroys a server structure. + * @param id: id of char-serv (should be >0, FIXME) + */ +void logchrif_server_destroy(int id){ + if( ch_server[id].fd != -1 ) { + do_close(ch_server[id].fd); + ch_server[id].fd = -1; + } +} + +/** + * Resets all the data related to a server. + * Actually destroys then recreates the struct. + * @param id: id of char-serv (should be >0, FIXME) + */ +void logchrif_server_reset(int id) { + online_db->foreach(online_db, login_online_db_setoffline, id); //Set all chars from this char server to offline. + logchrif_server_destroy(id); + logchrif_server_init(id); +} + +/** + * Called when the connection to Char Server is disconnected. + * @param id: id of char-serv (should be >0, FIXME) + */ +void logchrif_on_disconnect(int id) { + ShowStatus("Char-server '%s' has disconnected.\n", ch_server[id].name); + logchrif_server_reset(id); +} + +/** + * loginchrif constructor + * Initialisation, function called at start of the login-serv. + */ +void do_init_loginchrif(void){ + int i; + for( i = 0; i < ARRAYLENGTH(ch_server); ++i ) + logchrif_server_init(i); + + // add timer to detect ip address change and perform update + if (login_config.ip_sync_interval) { + add_timer_func_list(logchrif_sync_ip_addresses, "sync_ip_addresses"); + add_timer_interval(gettick() + login_config.ip_sync_interval, logchrif_sync_ip_addresses, 0, 0, login_config.ip_sync_interval); + } +} + +/** + * Signal handler + * This function attempts to properly close the server when an interrupt signal is received. + * current signal catch : SIGTERM, SIGINT + */ +void do_shutdown_loginchrif(void){ + int id; + for( id = 0; id < ARRAYLENGTH(ch_server); ++id ) + logchrif_server_reset(id); +} + +/** + * loginchrif destructor + * dealloc..., function called at exit of the login-serv + */ +void do_final_loginchrif(void){ + int i; + for( i = 0; i < ARRAYLENGTH(ch_server); ++i ) + logchrif_server_destroy(i); +} \ No newline at end of file diff --git a/src/login/loginchrif.h b/src/login/loginchrif.h new file mode 100644 index 0000000000..698be696ba --- /dev/null +++ b/src/login/loginchrif.h @@ -0,0 +1,56 @@ +/** + * @file loginchrif.h + * Module purpose is to handle incoming and outgoing requests with char-server. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams originally in login.c + * @author rAthena Dev Team + */ + +#ifndef LOGINCHRIF_H +#define LOGINCHRIF_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Entry point from char-server to log-server. + * Function that checks incoming command, then splits it to the correct handler. + * @param fd: file descriptor to parse, (link to char-serv) + * @return 0=invalid server,marked for disconnection,unknow packet; 1=success + */ +int logchrif_parse(int fd); + +/** + * Packet send to all char-servers, except one. (wos: without our self) + * @param sfd: fd to discard sending to + * @param buf: packet to send in form of an array buffer + * @param len: size of packet + * @return : the number of char-serv the packet was sent to + */ +int logchrif_sendallwos(int sfd, uint8* buf, size_t len); + +/** + * loginchrif constructor + * Initialisation, function called at start of the login-serv. + */ +void do_init_loginchrif(void); +/** + * Signal handler + * This function attempts to properly close the server when an interrupt signal is received. + * current signal catch : SIGTERM, SIGINT + */ +void do_shutdown_loginchrif(void); +/** + * loginchrif destructor + * dealloc..., function called at exit of the login-serv + */ +void do_final_loginchrif(void); + +#ifdef __cplusplus +} +#endif + +#endif /* LOGINCHRIF_H */ + diff --git a/src/login/loginclif.c b/src/login/loginclif.c new file mode 100644 index 0000000000..61caf4ba64 --- /dev/null +++ b/src/login/loginclif.c @@ -0,0 +1,564 @@ +/** + * @file loginclif.c + * Module purpose is to handle incoming and outgoing requests with client. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams originally in login.c + * @author rAthena Dev Team + */ + +#include "../common/timer.h" //difftick +#include "../common/strlib.h" //safeprint +#include "../common/showmsg.h" //show notice +#include "../common/socket.h" //wfifo session +#include "../common/malloc.h" +#include "../common/utils.h" +#include "../common/md5calc.h" +#include "../common/random.h" +#include "account.h" +#include "ipban.h" //ipban_check +#include "login.h" +#include "loginlog.h" +#include "loginclif.h" +#include "loginchrif.h" + +#include +#include +#include + +/** + * Transmit auth result to client. + * @param fd: client file desciptor link + * @param result: result to transmit to client, see below + * 1 : Server closed + * 2 : Someone has already logged in with this id + * 8 : already online + * .B (SC_NOTIFY_BAN) + */ +static void logclif_sent_auth_result(int fd,char result){ + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x81; + WFIFOB(fd,2) = result; + WFIFOSET(fd,3); +} + +/** + * Auth successful, inform client and create a temp auth_node. + * @param sd: player session + */ +static void logclif_auth_ok(struct login_session_data* sd) { + int fd = sd->fd; + uint32 ip = session[fd]->client_addr; + + uint8 server_num, n; + uint32 subnet_char_ip; + struct auth_node* node; + int i; + + if( runflag != LOGINSERVER_ST_RUNNING ){ + // players can only login while running + logclif_sent_auth_result(fd,1); // server closed + return; + } + + if( login_config.group_id_to_connect >= 0 && sd->group_id != login_config.group_id_to_connect ) { + ShowStatus("Connection refused: the required group id for connection is %d (account: %s, group: %d).\n", login_config.group_id_to_connect, sd->userid, sd->group_id); + logclif_sent_auth_result(fd,1); // server closed + return; + } else if( login_config.min_group_id_to_connect >= 0 && login_config.group_id_to_connect == -1 && sd->group_id < login_config.min_group_id_to_connect ) { + ShowStatus("Connection refused: the minium group id required for connection is %d (account: %s, group: %d).\n", login_config.min_group_id_to_connect, sd->userid, sd->group_id); + logclif_sent_auth_result(fd,1); // server closed + return; + } + + server_num = 0; + for( i = 0; i < ARRAYLENGTH(ch_server); ++i ) + if( session_isActive(ch_server[i].fd) ) + server_num++; + + if( server_num == 0 ) + {// if no char-server, don't send void list of servers, just disconnect the player with proper message + ShowStatus("Connection refused: there is no char-server online (account: %s).\n", sd->userid); + logclif_sent_auth_result(fd,1); // server closed + return; + } + + { + struct online_login_data* data = (struct online_login_data*)idb_get(online_db, sd->account_id); + if( data ) + {// account is already marked as online! + if( data->char_server > -1 ) + {// Request char servers to kick this account out. [Skotlex] + uint8 buf[6]; + ShowNotice("User '%s' is already online - Rejected.\n", sd->userid); + WBUFW(buf,0) = 0x2734; + WBUFL(buf,2) = sd->account_id; + logchrif_sendallwos(-1, buf, 6); + if( data->waiting_disconnect == INVALID_TIMER ) + data->waiting_disconnect = add_timer(gettick()+AUTH_TIMEOUT, login_waiting_disconnect_timer, sd->account_id, 0); + logclif_sent_auth_result(fd,8); // 08 = Server still recognizes your last login + return; + } + else + if( data->char_server == -1 ) + {// client has authed but did not access char-server yet + // wipe previous session + idb_remove(auth_db, sd->account_id); + login_remove_online_user(sd->account_id); + data = NULL; + } + } + } + + login_log(ip, sd->userid, 100, "login ok"); + ShowStatus("Connection of the account '%s' accepted.\n", sd->userid); + + WFIFOHEAD(fd,47+32*server_num); + WFIFOW(fd,0) = 0x69; + WFIFOW(fd,2) = 47+32*server_num; + WFIFOL(fd,4) = sd->login_id1; + WFIFOL(fd,8) = sd->account_id; + WFIFOL(fd,12) = sd->login_id2; + WFIFOL(fd,16) = 0; // in old version, that was for ip (not more used) + //memcpy(WFIFOP(fd,20), sd->lastlogin, 24); // in old version, that was for name (not more used) + memset(WFIFOP(fd,20), 0, 24); + WFIFOW(fd,44) = 0; // unknown + WFIFOB(fd,46) = sex_str2num(sd->sex); + for( i = 0, n = 0; i < ARRAYLENGTH(ch_server); ++i ) { + if( !session_isValid(ch_server[i].fd) ) + continue; + subnet_char_ip = lan_subnetcheck(ip); // Advanced subnet check [LuzZza] + WFIFOL(fd,47+n*32) = htonl((subnet_char_ip) ? subnet_char_ip : ch_server[i].ip); + WFIFOW(fd,47+n*32+4) = ntows(htons(ch_server[i].port)); // [!] LE byte order here [!] + memcpy(WFIFOP(fd,47+n*32+6), ch_server[i].name, 20); + WFIFOW(fd,47+n*32+26) = ch_server[i].users; + WFIFOW(fd,47+n*32+28) = ch_server[i].type; + WFIFOW(fd,47+n*32+30) = ch_server[i].new_; + n++; + } + WFIFOSET(fd,47+32*server_num); + + // create temporary auth entry + CREATE(node, struct auth_node, 1); + node->account_id = sd->account_id; + node->login_id1 = sd->login_id1; + node->login_id2 = sd->login_id2; + node->sex = sd->sex; + node->ip = ip; + node->version = sd->version; + node->clienttype = sd->clienttype; + idb_put(auth_db, sd->account_id, node); + { + struct online_login_data* data; + // mark client as 'online' + data = login_add_online_user(-1, sd->account_id); + // schedule deletion of this node + data->waiting_disconnect = add_timer(gettick()+AUTH_TIMEOUT, login_waiting_disconnect_timer, sd->account_id, 0); + } +} + +/** + * Inform client that auth has failed. + * @param sd: player session + * @param result: nb (msg define in conf) + 0 = Unregistered ID + 1 = Incorrect Password + 2 = This ID is expired + 3 = Rejected from Server + 4 = You have been blocked by the GM Team + 5 = Your Game's EXE file is not the latest version + 6 = Your are Prohibited to log in until %s + 7 = Server is jammed due to over populated + 8 = No more accounts may be connected from this company + 9 = MSI_REFUSE_BAN_BY_DBA + 10 = MSI_REFUSE_EMAIL_NOT_CONFIRMED + 11 = MSI_REFUSE_BAN_BY_GM + 12 = MSI_REFUSE_TEMP_BAN_FOR_DBWORK + 13 = MSI_REFUSE_SELF_LOCK + 14 = MSI_REFUSE_NOT_PERMITTED_GROUP + 15 = MSI_REFUSE_NOT_PERMITTED_GROUP + 99 = This ID has been totally erased + 100 = Login information remains at %s + 101 = Account has been locked for a hacking investigation. Please contact the GM Team for more information + 102 = This account has been temporarily prohibited from login due to a bug-related investigation + 103 = This character is being deleted. Login is temporarily unavailable for the time being + 104 = This character is being deleted. Login is temporarily unavailable for the time being + default = Unknown Error. + */ +static void logclif_auth_failed(struct login_session_data* sd, int result) { + int fd = sd->fd; + uint32 ip = session[fd]->client_addr; + + if (login_config.log_login) + { + if(result >= 0 && result <= 15) + login_log(ip, sd->userid, result, msg_txt(result)); + else if(result >= 99 && result <= 104) + login_log(ip, sd->userid, result, msg_txt(result-83)); //-83 offset + else + login_log(ip, sd->userid, result, msg_txt(22)); //unknow error + } + + if( result == 1 && login_config.dynamic_pass_failure_ban ) + ipban_log(ip); // log failed password attempt + +//#if PACKETVER >= 20120000 /* not sure when this started */ + if( sd->version >= date2version(20120000) ){ /* not sure when this started */ + WFIFOHEAD(fd,26); + WFIFOW(fd,0) = 0x83e; + WFIFOL(fd,2) = result; + if( result != 6 ) + memset(WFIFOP(fd,6), '\0', 20); + else { // 6 = Your are Prohibited to log in until %s + struct mmo_account acc; + AccountDB* accounts = login_get_accounts_db(); + time_t unban_time = ( accounts->load_str(accounts, &acc, sd->userid) ) ? acc.unban_time : 0; + timestamp2string((char*)WFIFOP(fd,6), 20, unban_time, login_config.date_format); + } + WFIFOSET(fd,26); + } +//#else + else { + WFIFOHEAD(fd,23); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = (uint8)result; + if( result != 6 ) + memset(WFIFOP(fd,3), '\0', 20); + else { // 6 = Your are Prohibited to log in until %s + struct mmo_account acc; + AccountDB* accounts = login_get_accounts_db(); + time_t unban_time = ( accounts->load_str(accounts, &acc, sd->userid) ) ? acc.unban_time : 0; + timestamp2string((char*)WFIFOP(fd,3), 20, unban_time, login_config.date_format); + } + WFIFOSET(fd,23); + } +//#endif +} + +/** + * Received a keepalive packet to maintain connection. + * 0x200 .24B. + * @param fd: fd to parse from (client fd) + * @return 0 not enough info transmitted, 1 success + */ +static int logclif_parse_keepalive(int fd){ + if (RFIFOREST(fd) < 26) + return 0; + RFIFOSKIP(fd,26); + return 1; +} + +/** + * Received a keepalive packet to maintain connection. + * S 0204 .16B (kRO 2004-05-31aSakexe langtype 0 and 6) + * @param fd: fd to parse from (client fd) + * @return 0 not enough info transmitted, 1 success + */ +static int logclif_parse_updclhash(int fd, struct login_session_data *sd){ + if (RFIFOREST(fd) < 18) + return 0; + sd->has_client_hash = 1; + memcpy(sd->client_hash, RFIFOP(fd, 2), 16); + RFIFOSKIP(fd,18); + return 1; +} + +/** + * Received a connection request. + * @param fd: file descriptor to parse from (client) + * @param sd: client session + * @param command: packet type sent + * @param ip: ipv4 address (client) + * S 0064 .L .24B .24B .B + * S 0277 .L .24B .24B .B .16B .13B + * S 02b0 .L .24B .24B .B .16B .13B .B + * S 01dd .L .24B .16B .B + * S 01fa .L .24B .16B .B .B(index of the connection in the clientinfo file (+10 if the command-line contains "pc")) + * S 027c .L .24B .16B .B .13B(junk) + * S 0825 .W .L .B .24B .27B .17B .15B .(packetsize - 0x5C)B + * @param fd: fd to parse from (client fd) + * @return 0 failure, 1 success + */ +static int logclif_parse_reqauth(int fd, struct login_session_data *sd, int command, char* ip){ + size_t packet_len = RFIFOREST(fd); + + if( (command == 0x0064 && packet_len < 55) + || (command == 0x0277 && packet_len < 84) + || (command == 0x02b0 && packet_len < 85) + || (command == 0x01dd && packet_len < 47) + || (command == 0x01fa && packet_len < 48) + || (command == 0x027c && packet_len < 60) + || (command == 0x0825 && (packet_len < 4 || packet_len < RFIFOW(fd, 2))) ) + return 0; + else { + int result; + uint32 version; + char username[NAME_LENGTH]; + char password[PASSWD_LENGTH]; + unsigned char passhash[16]; + uint8 clienttype; + bool israwpass = (command==0x0064 || command==0x0277 || command==0x02b0 || command == 0x0825); + + // Shinryo: For the time being, just use token as password. + if(command == 0x0825) { + char *accname = (char *)RFIFOP(fd, 9); + char *token = (char *)RFIFOP(fd, 0x5C); + size_t uAccLen = strlen(accname); + size_t uTokenLen = RFIFOREST(fd) - 0x5C; + + version = RFIFOL(fd,4); + + if(uAccLen > NAME_LENGTH - 1 || uAccLen <= 0 || uTokenLen > NAME_LENGTH - 1 || uTokenLen <= 0) + { + logclif_auth_failed(sd, 3); + return 0; + } + + safestrncpy(username, accname, uAccLen + 1); + safestrncpy(password, token, uTokenLen + 1); + clienttype = RFIFOB(fd, 8); + } + else + { + version = RFIFOL(fd,2); + safestrncpy(username, (const char*)RFIFOP(fd,6), NAME_LENGTH); + if( israwpass ) + { + safestrncpy(password, (const char*)RFIFOP(fd,30), PASSWD_LENGTH); + clienttype = RFIFOB(fd,54); + } + else + { + memcpy(passhash, RFIFOP(fd,30), 16); + clienttype = RFIFOB(fd,46); + } + } + RFIFOSKIP(fd,RFIFOREST(fd)); // assume no other packet was sent + + sd->clienttype = clienttype; + sd->version = version; + safestrncpy(sd->userid, username, NAME_LENGTH); + if( israwpass ) + { + ShowStatus("Request for connection of %s (ip: %s) version=%d\n", sd->userid, ip,sd->version); + safestrncpy(sd->passwd, password, NAME_LENGTH); + if( login_config.use_md5_passwds ) + MD5_String(sd->passwd, sd->passwd); + sd->passwdenc = 0; + } + else + { + ShowStatus("Request for connection (passwdenc mode) of %s (ip: %s) version=%d\n", sd->userid, ip,sd->version); + bin2hex(sd->passwd, passhash, 16); // raw binary data here! + sd->passwdenc = PASSWORDENC; + } + + if( sd->passwdenc != 0 && login_config.use_md5_passwds ) + { + logclif_auth_failed(sd, 3); // send "rejected from server" + return 0; + } + + result = login_mmo_auth(sd, false); + + if( result == -1 ) + logclif_auth_ok(sd); + else + logclif_auth_failed(sd, result); + } + return 1; +} + +/** + * Client requests an md5key for his session: keys will be generated and sent back. + * @param fd: file descriptor to parse from (client) + * @param sd: client session + * @return 1 success + */ +static int logclif_parse_reqkey(int fd, struct login_session_data *sd){ + RFIFOSKIP(fd,2); + { + memset(sd->md5key, '\0', sizeof(sd->md5key)); + sd->md5keylen = (uint16)(12 + rnd() % 4); + MD5_Salt(sd->md5keylen, sd->md5key); + + WFIFOHEAD(fd,4 + sd->md5keylen); + WFIFOW(fd,0) = 0x01dc; + WFIFOW(fd,2) = 4 + sd->md5keylen; + memcpy(WFIFOP(fd,4), sd->md5key, sd->md5keylen); + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 1; +} + +/** + * Char-server request to connect to the login-server. + * This is needed to exchange packets. + * @param fd: file descriptor to parse from (client) + * @param sd: client session + * @param ip: ipv4 address (client) + * @return 0 packet received too shirt, 1 success + */ +static int logclif_parse_reqcharconnec(int fd, struct login_session_data *sd, char* ip){ + if (RFIFOREST(fd) < 86) + return 0; + { + int result; + char server_name[20]; + char message[256]; + uint32 server_ip; + uint16 server_port; + uint16 type; + uint16 new_; + + safestrncpy(sd->userid, (char*)RFIFOP(fd,2), NAME_LENGTH); + safestrncpy(sd->passwd, (char*)RFIFOP(fd,26), NAME_LENGTH); + if( login_config.use_md5_passwds ) + MD5_String(sd->passwd, sd->passwd); + sd->passwdenc = 0; + sd->version = login_config.client_version_to_connect; // hack to skip version check + server_ip = ntohl(RFIFOL(fd,54)); + server_port = ntohs(RFIFOW(fd,58)); + safestrncpy(server_name, (char*)RFIFOP(fd,60), 20); + type = RFIFOW(fd,82); + new_ = RFIFOW(fd,84); + RFIFOSKIP(fd,86); + + ShowInfo("Connection request of the char-server '%s' @ %u.%u.%u.%u:%u (account: '%s', pass: '%s', ip: '%s')\n", server_name, CONVIP(server_ip), server_port, sd->userid, sd->passwd, ip); + sprintf(message, "charserver - %s@%u.%u.%u.%u:%u", server_name, CONVIP(server_ip), server_port); + login_log(session[fd]->client_addr, sd->userid, 100, message); + + result = login_mmo_auth(sd, true); + if( runflag == LOGINSERVER_ST_RUNNING && + result == -1 && + sd->sex == 'S' && + sd->account_id >= 0 && sd->account_id < ARRAYLENGTH(ch_server) && + !session_isValid(ch_server[sd->account_id].fd) ) + { + ShowStatus("Connection of the char-server '%s' accepted.\n", server_name); + safestrncpy(ch_server[sd->account_id].name, server_name, sizeof(ch_server[sd->account_id].name)); + ch_server[sd->account_id].fd = fd; + ch_server[sd->account_id].ip = server_ip; + ch_server[sd->account_id].port = server_port; + ch_server[sd->account_id].users = 0; + ch_server[sd->account_id].type = type; + ch_server[sd->account_id].new_ = new_; + + session[fd]->func_parse = logchrif_parse; + session[fd]->flag.server = 1; + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + + // send connection success + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x2711; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + } + else + { + ShowNotice("Connection of the char-server '%s' REFUSED.\n", server_name); + WFIFOHEAD(fd,3); + WFIFOW(fd,0) = 0x2711; + WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + } + } + return 1; +} + +/** + * Entry point from client to log-server. + * Function that checks incoming command, then splits it to the correct handler. + * @param fd: file descriptor to parse, (link to client) + * @return 0=invalid session,marked for disconnection,unknow packet, banned..; 1=success + */ +int logclif_parse(int fd) { + struct login_session_data* sd = (struct login_session_data*)session[fd]->session_data; + + char ip[16]; + uint32 ipl = session[fd]->client_addr; + ip2str(ipl, ip); + + if( session[fd]->flag.eof ) + { + ShowInfo("Closed connection from '"CL_WHITE"%s"CL_RESET"'.\n", ip); + do_close(fd); + return 0; + } + + if( sd == NULL ) + { + // Perform ip-ban check + if( login_config.ipban && ipban_check(ipl) ) + { + ShowStatus("Connection refused: IP isn't authorised (deny/allow, ip: %s).\n", ip); + login_log(ipl, "unknown", -3, "ip banned"); + WFIFOHEAD(fd,23); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 3; // 3 = Rejected from Server + WFIFOSET(fd,23); + set_eof(fd); + return 0; + } + // create a session for this new connection + CREATE(session[fd]->session_data, struct login_session_data, 1); + sd = (struct login_session_data*)session[fd]->session_data; + sd->fd = fd; + } + + while( RFIFOREST(fd) >= 2 ) + { + uint16 command = RFIFOW(fd,0); + int next=1; + + switch( command ) + { + // New alive packet: used to verify if client is always alive. + case 0x0200: next = logclif_parse_keepalive(fd); break; + // client md5 hash (binary) + case 0x0204: next = logclif_parse_updclhash(fd,sd); break; + // request client login (raw password) + case 0x0064: // S 0064 .L .24B .24B .B + case 0x0277: // S 0277 .L .24B .24B .B .16B .13B + case 0x02b0: // S 02b0 .L .24B .24B .B .16B .13B .B + // request client login (md5-hashed password) + case 0x01dd: // S 01dd .L .24B .16B .B + case 0x01fa: // S 01fa .L .24B .16B .B .B(index of the connection in the clientinfo file (+10 if the command-line contains "pc")) + case 0x027c: // S 027c .L .24B .16B .B .13B(junk) + case 0x0825: // S 0825 .W .L .B .24B .27B .17B .15B .(packetsize - 0x5C)B + next = logclif_parse_reqauth(fd, sd, command, ip); + break; + // Sending request of the coding key + case 0x01db: next = logclif_parse_reqkey(fd, sd); break; + // Connection request of a char-server + case 0x2710: logclif_parse_reqcharconnec(fd,sd, ip); return 0; // processing will continue elsewhere + default: + ShowNotice("Abnormal end of connection (ip: %s): Unknown packet 0x%x\n", ip, command); + set_eof(fd); + return 0; + } + if(next==0) return 0; // avoid processing of followup packets (prev was probably incomplete) + } + + return 0; +} + + + +/// Constructor destructor + +/** + * Initialize the module. + * Launched at login-serv start, create db or other long scope variable here. + */ +void do_init_loginclif(void){ + return; +} + +/** + * loginclif destructor + * dealloc..., function called at exit of the login-serv + */ +void do_final_loginclif(void){ + return; +} \ No newline at end of file diff --git a/src/login/loginclif.h b/src/login/loginclif.h new file mode 100644 index 0000000000..2fe1aaef22 --- /dev/null +++ b/src/login/loginclif.h @@ -0,0 +1,42 @@ +/** + * @file loginclif.h + * Module purpose is to handle incoming and outgoing requests with client. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams originally in login.c + * @author rAthena Dev Team + */ + +#ifndef _LOGINCLIF_H +#define _LOGINCLIF_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Entry point from client to log-server. + * Function that checks incoming command, then splits it to the correct handler. + * @param fd: file descriptor to parse, (link to client) + * @return 0=invalid session,marked for disconnection,unknow packet, banned..; 1=success + */ +int logclif_parse(int fd); + +/** + * Initialize the module. + * Launched at login-serv start, create db or other long scope variable here. + */ +void do_init_loginclif(void); + +/** + * loginclif destructor + * dealloc..., function called at exit of the login-serv + */ +void do_final_loginclif(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _LOGINCLIF_H */ + diff --git a/src/login/logincnslif.c b/src/login/logincnslif.c new file mode 100644 index 0000000000..19b3abcfcf --- /dev/null +++ b/src/login/logincnslif.c @@ -0,0 +1,176 @@ +/** + * @file logincnslif.c + * Module purpose is to handle incoming and outgoing requests with console. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams originally in login.c + * @author rAthena Dev Team + */ + +#include "../common/mmo.h" //cbasetype + NAME_LENGTH +#include "../common/showmsg.h" //show notice +#include "../common/md5calc.h" +#include "../common/ers.h" +#include "../common/cli.h" +#include "../common/timer.h" +#include "../common/strlib.h" +#include "login.h" +#include "logincnslif.h" + +#include +#include +#include + +/** + * Login-server console help: starting option info. + * Do not rename function used as extern. + * @param do_exit: terminate program execution ? + */ +void display_helpscreen(bool do_exit) { + ShowInfo("Usage: %s [options]\n", SERVER_NAME); + ShowInfo("\n"); + ShowInfo("Options:\n"); + ShowInfo(" -?, -h [--help]\t\tDisplays this help screen.\n"); + ShowInfo(" -v [--version]\t\tDisplays the server's version.\n"); + ShowInfo(" --run-once\t\t\tCloses server after loading (testing).\n"); + ShowInfo(" --login-config \t\tAlternative login-server configuration.\n"); + ShowInfo(" --lan-config \t\tAlternative lag configuration.\n"); + ShowInfo(" --msg-config \t\tAlternative message configuration.\n"); + if( do_exit ) + exit(EXIT_SUCCESS); +} + +/** + * Read the option specified in command line + * and assign the confs used by the different server. + * @param argc: + * @param argv: + * @return true or Exit on failure. + */ +int logcnslif_get_options(int argc, char ** argv) { + int i = 0; + for (i = 1; i < argc; i++) { + const char* arg = argv[i]; + + if (arg[0] != '-' && (arg[0] != '/' || arg[1] == '-')) {// -, -- and / + ShowError("Unknown option '%s'.\n", argv[i]); + exit(EXIT_FAILURE); + } else if ((++arg)[0] == '-') {// long option + arg++; + + if (strcmp(arg, "help") == 0) { + display_helpscreen(true); + } else if (strcmp(arg, "version") == 0) { + display_versionscreen(true); + } else if (strcmp(arg, "run-once") == 0){ // close the map-server as soon as its done.. for testing [Celest] + runflag = CORE_ST_STOP; + } else if (SERVER_TYPE & (ATHENA_SERVER_LOGIN)) { //login + if (strcmp(arg, "lan-config") == 0) { + if (opt_has_next_value(arg, i, argc)) safestrncpy(login_config.lanconf_name, argv[++i], sizeof(login_config.lanconf_name)); + } + if (strcmp(arg, "login-config") == 0) { + if (opt_has_next_value(arg, i, argc)) safestrncpy(login_config.loginconf_name, argv[++i], sizeof(login_config.loginconf_name)); + } + if (strcmp(arg, "msg-config") == 0) { + if (opt_has_next_value(arg, i, argc)) safestrncpy(login_config.msgconf_name, argv[++i], sizeof(login_config.msgconf_name)); + } else { + ShowError("Unknown option '%s'.\n", argv[i]); + exit(EXIT_FAILURE); + } + } + } else switch (arg[0]) {// short option + case '?': + case 'h': + display_helpscreen(true); + break; + case 'v': + display_versionscreen(true); + break; + default: + ShowError("Unknown option '%s'.\n", argv[i]); + exit(EXIT_FAILURE); + } + } + return 1; +} + +/** + * Console Command Parser + * Transmited from command cli.c + * note common name for all serv do not rename (extern in cli) + * @author [Wizputer] + * @param buf: buffer to parse, (from console) + * @return 1=success + */ +int cnslif_parse(const char* buf){ + char type[64]; + char command[64]; + int n=0; + + if( ( n = sscanf(buf, "%127[^:]:%255[^\n\r]", type, command) ) < 2 ){ + if((n = sscanf(buf, "%63[^\n]", type))<1) return -1; //nothing to do no arg + } + if( n != 2 ){ //end string + ShowNotice("Type: '%s'\n",type); + command[0] = '\0'; + } + else + ShowNotice("Type of command: '%s' || Command: '%s'\n",type,command); + + if( n == 2 ){ + if(strcmpi("server", type) == 0 ){ + if( strcmpi("shutdown", command) == 0 || strcmpi("exit", command) == 0 || strcmpi("quit", command) == 0 ){ + runflag = 0; + } + else if( strcmpi("alive", command) == 0 || strcmpi("status", command) == 0 ) + ShowInfo(CL_CYAN"Console: "CL_BOLD"I'm Alive."CL_RESET"\n"); + } + if( strcmpi("create",type) == 0 ) + { + char username[NAME_LENGTH], password[NAME_LENGTH], md5password[32+1], sex; //23+1 plaintext 32+1 md5 + bool md5 = 0; + if( sscanf(command, "%23s %23s %c", username, password, &sex) < 3 || strnlen(username, sizeof(username)) < 4 || strnlen(password, sizeof(password)) < 1 ){ + ShowWarning("Console: Invalid parameters for '%s'. Usage: %s \n", type, type); + return 0; + } + if( login_config.use_md5_passwds ){ + MD5_String(password,md5password); + md5 = 1; + } + if( login_mmo_auth_new(username,(md5?md5password:password), TOUPPER(sex), "0.0.0.0") != -1 ){ + ShowError("Console: Account creation failed.\n"); + return 0; + } + ShowStatus("Console: Account '%s' created successfully.\n", username); + } + } + else if( strcmpi("ers_report", type) == 0 ){ + ers_report(); + } + else if( strcmpi("help", type) == 0 ){ + ShowInfo("Available commands:\n"); + ShowInfo("\t server:shutdown => Stops the server.\n"); + ShowInfo("\t server:alive => Checks if the server is running.\n"); + ShowInfo("\t ers_report => Displays database usage.\n"); + ShowInfo("\t create: => Creates a new account.\n"); + } + return 1; +} + +/** + * Initialize the module. + * Launched at login-serv start, create db or other long scope variable here. + */ +void do_init_logincnslif(void){ + if( login_config.console ) { + add_timer_func_list(parse_console_timer, "parse_console_timer"); + add_timer_interval(gettick()+1000, parse_console_timer, 0, 0, 1000); //start in 1s each 1sec + } +} + +/** + * Handler to cleanup module, called when login-server stops. + */ +void do_final_logincnslif(void){ + return; +} \ No newline at end of file diff --git a/src/login/logincnslif.h b/src/login/logincnslif.h new file mode 100644 index 0000000000..67faf620be --- /dev/null +++ b/src/login/logincnslif.h @@ -0,0 +1,58 @@ +/** + * @file logincnslif.h + * Module purpose is to handle incoming and outgoing requests with console. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams originally in login.c + * @author rAthena Dev Team + */ + +#ifndef CONSOLEIF_H +#define CONSOLEIF_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Console Command Parser + * Transmited from command cli.c + * note common name for all serv do not rename (extern in cli) + * @author [Wizputer] + * @param buf: buffer to parse, (from console) + * @return 1=success + */ +int cnslif_parse(const char* buf); + +/** + * Read the option specified in command line + * and assign the confs used by the different server. + * @param argc: + * @param argv: + * @return true or Exit on failure. + */ +int logcnslif_get_options(int argc, char ** argv); + +/** + * Login-server console help: starting option info. + * Do not rename function used as extern. + * @param do_exit: terminate program execution ? + */ +void display_helpscreen(bool do_exit); + +/** + * Initialize the module. + * Launched at login-serv start, create db or other long scope variable here. + */ +void do_init_logincnslif(void); +/** + * Handler to cleanup module, called when login-server stops. + */ +void do_final_logincnslif(void); + +#ifdef __cplusplus +} +#endif + +#endif /* CONSOLEIF_H */ + diff --git a/src/login/loginlog_sql.c b/src/login/loginlog.c similarity index 77% rename from src/login/loginlog_sql.c rename to src/login/loginlog.c index 90c585df49..640238bac1 100644 --- a/src/login/loginlog_sql.c +++ b/src/login/loginlog.c @@ -1,5 +1,11 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder +/** + * @file loginlog.c + * Module purpose is to register (log) events into a file or sql database. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams rev < 15k + * @author rAthena Dev Team + */ #include "../common/cbasetypes.h" #include "../common/mmo.h" @@ -13,7 +19,7 @@ static char global_db_hostname[32] = "127.0.0.1"; static uint16 global_db_port = 3306; static char global_db_username[32] = "ragnarok"; -static char global_db_password[32] = ""; +static char global_db_password[32] = ""; //empty by default since mysql is empty by default as well static char global_db_database[32] = "ragnarok"; static char global_codepage[32] = ""; // local sql settings @@ -29,9 +35,13 @@ static Sql* sql_handle = NULL; static bool enabled = false; -// Returns the number of failed login attemps by the ip in the last minutes. -unsigned long loginlog_failedattempts(uint32 ip, unsigned int minutes) -{ +/** + * Get the number of failed login attempts by the ip in the last minutes. + * @param ip: ip to search attempt from + * @param minutes: intervall to search + * @return number of failed attempts + */ +unsigned long loginlog_failedattempts(uint32 ip, unsigned int minutes) { unsigned long failures = 0; if( !enabled ) @@ -52,11 +62,14 @@ unsigned long loginlog_failedattempts(uint32 ip, unsigned int minutes) } -/*============================================= - * Records an event in the login log - *---------------------------------------------*/ -void login_log(uint32 ip, const char* username, int rcode, const char* message) -{ +/** + * Records an event in the login log. + * @param ip: + * @param username: + * @param rcode: + * @param message: + */ +void login_log(uint32 ip, const char* username, int rcode, const char* message) { char esc_username[NAME_LENGTH*2+1]; char esc_message[255*2+1]; int retcode; @@ -75,60 +88,13 @@ void login_log(uint32 ip, const char* username, int rcode, const char* message) Sql_ShowDebug(sql_handle); } -bool loginlog_init(void) -{ - const char* username; - const char* password; - const char* hostname; - uint16 port; - const char* database; - const char* codepage; - - if( log_db_hostname[0] != '\0' ) - {// local settings - username = log_db_username; - password = log_db_password; - hostname = log_db_hostname; - port = log_db_port; - database = log_db_database; - codepage = log_codepage; - } - else - {// global settings - username = global_db_username; - password = global_db_password; - hostname = global_db_hostname; - port = global_db_port; - database = global_db_database; - codepage = global_codepage; - } - - sql_handle = Sql_Malloc(); - - if( SQL_ERROR == Sql_Connect(sql_handle, username, password, hostname, port, database) ) - { - Sql_ShowDebug(sql_handle); - Sql_Free(sql_handle); - exit(EXIT_FAILURE); - } - - if( codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, codepage) ) - Sql_ShowDebug(sql_handle); - - enabled = true; - - return true; -} - -bool loginlog_final(void) -{ - Sql_Free(sql_handle); - sql_handle = NULL; - return true; -} - -bool loginlog_config_read(const char* key, const char* value) -{ +/** + * Read configuration options. + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if config not complete or server already running + */ +bool loginlog_config_read(const char* key, const char* value) { const char* signature; signature = "sql."; @@ -182,3 +148,67 @@ bool loginlog_config_read(const char* key, const char* value) return true; } + + +/// Constructor destructor + +/** + * Initialize the module. + * Launched at login-serv start, create db or other long scope variable here. + * @return true if success else exit execution + */ +bool loginlog_init(void) { + const char* username; + const char* password; + const char* hostname; + uint16 port; + const char* database; + const char* codepage; + + if( log_db_hostname[0] != '\0' ) + {// local settings + username = log_db_username; + password = log_db_password; + hostname = log_db_hostname; + port = log_db_port; + database = log_db_database; + codepage = log_codepage; + } + else + {// global settings + username = global_db_username; + password = global_db_password; + hostname = global_db_hostname; + port = global_db_port; + database = global_db_database; + codepage = global_codepage; + } + + sql_handle = Sql_Malloc(); + + if( SQL_ERROR == Sql_Connect(sql_handle, username, password, hostname, port, database) ) + { + Sql_ShowDebug(sql_handle); + Sql_Free(sql_handle); + exit(EXIT_FAILURE); + } + + if( codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, codepage) ) + Sql_ShowDebug(sql_handle); + + enabled = true; + + return true; +} + + +/** + * Handler to cleanup module, called when login-server stops. + * Currently closing sql connection to log schema. + * @return true success + */ +bool loginlog_final(void) { + Sql_Free(sql_handle); + sql_handle = NULL; + return true; +} \ No newline at end of file diff --git a/src/login/loginlog.h b/src/login/loginlog.h index a1ffaae85c..1acdf69cb2 100644 --- a/src/login/loginlog.h +++ b/src/login/loginlog.h @@ -1,15 +1,61 @@ -// Copyright (c) Athena Dev Teams - Licensed under GNU GPL -// For more information, see LICENCE in the main folder +/** + * @file loginlog.h + * Module purpose is to register (log) events into a file or sql database. + * Licensed under GNU GPL. + * For more information, see LICENCE in the main folder. + * @author Athena Dev Teams rev < 15k + * @author rAthena Dev Team + */ #ifndef __LOGINLOG_H_INCLUDED__ #define __LOGINLOG_H_INCLUDED__ +#ifdef __cplusplus +extern "C" { +#endif +/** + * Get the number of failed login attempts by the ip in the last minutes. + * @param ip: ip to search attempt from + * @param minutes: intervall to search + * @return number of failed attempts + */ unsigned long loginlog_failedattempts(uint32 ip, unsigned int minutes); + +/** + * Records an event in the login log. + * @param ip: + * @param username: + * @param rcode: + * @param message: + */ void login_log(uint32 ip, const char* username, int rcode, const char* message); -bool loginlog_init(void); -bool loginlog_final(void); + +/** + * Read configuration options. + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if config not complete or server already running + */ bool loginlog_config_read(const char* w1, const char* w2); +/** + * Initialize the module. + * Launched at login-serv start, create db or other long scope variable here. + * @return true if success else exit execution + */ +bool loginlog_init(void); + +/** + * Handler to cleanup module, called when login-server stops. + * atm closing sql connection to log schema + * @return true success + */ +bool loginlog_final(void); + +#ifdef __cplusplus +} +#endif + #endif // __LOGINLOG_H_INCLUDED__ diff --git a/src/login/sql/CMakeLists.txt b/src/login/sql/CMakeLists.txt index 1355f17eed..3da5f4bc74 100644 --- a/src/login/sql/CMakeLists.txt +++ b/src/login/sql/CMakeLists.txt @@ -8,13 +8,19 @@ set( SQL_LOGIN_HEADERS "${SQL_LOGIN_SOURCE_DIR}/account.h" "${SQL_LOGIN_SOURCE_DIR}/ipban.h" "${SQL_LOGIN_SOURCE_DIR}/login.h" + "${SQL_LOGIN_SOURCE_DIR}/loginclif.h" + "${SQL_LOGIN_SOURCE_DIR}/loginchrif.h" + "${SQL_LOGIN_SOURCE_DIR}/logincnslif.h" "${SQL_LOGIN_SOURCE_DIR}/loginlog.h" ) set( SQL_LOGIN_SOURCES - "${SQL_LOGIN_SOURCE_DIR}/account_sql.c" - "${SQL_LOGIN_SOURCE_DIR}/ipban_sql.c" + "${SQL_LOGIN_SOURCE_DIR}/account.c" + "${SQL_LOGIN_SOURCE_DIR}/ipban.c" "${SQL_LOGIN_SOURCE_DIR}/login.c" - "${SQL_LOGIN_SOURCE_DIR}/loginlog_sql.c" + "${SQL_LOGIN_SOURCE_DIR}/loginclif.c" + "${SQL_LOGIN_SOURCE_DIR}/loginchrif.c" + "${SQL_LOGIN_SOURCE_DIR}/logincnslif.c" + "${SQL_LOGIN_SOURCE_DIR}/loginlog.c" ) set( DEPENDENCIES common_sql ) set( LIBRARIES ${GLOBAL_LIBRARIES} ) diff --git a/vcproj-10/login-server_sql.vcxproj b/vcproj-10/login-server_sql.vcxproj index e0d9f92793..f3dfc8ac1a 100644 --- a/vcproj-10/login-server_sql.vcxproj +++ b/vcproj-10/login-server_sql.vcxproj @@ -147,6 +147,9 @@ + + + @@ -178,10 +181,13 @@ - - + + - + + + + diff --git a/vcproj-12/login-server_sql.vcxproj b/vcproj-12/login-server_sql.vcxproj index c4d9c892c9..ef3b9a7b76 100644 --- a/vcproj-12/login-server_sql.vcxproj +++ b/vcproj-12/login-server_sql.vcxproj @@ -151,6 +151,9 @@ + + + @@ -181,10 +184,13 @@ - - + + - + + + + diff --git a/vcproj-9/login-server_sql.vcproj b/vcproj-9/login-server_sql.vcproj index 03d72ef30c..fe8598bec5 100644 --- a/vcproj-9/login-server_sql.vcproj +++ b/vcproj-9/login-server_sql.vcproj @@ -211,7 +211,7 @@ > + + + + + + + + + + + +