874 lines
27 KiB
C++
874 lines
27 KiB
C++
// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
|
|
// For more information, see LICENCE in the main folder
|
|
|
|
#pragma warning(disable:4800)
|
|
#include "login.hpp"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <string>
|
|
|
|
#include "../common/cli.hpp"
|
|
#include "../common/core.hpp"
|
|
#include "../common/db.hpp"
|
|
#include "../common/malloc.hpp"
|
|
#include "../common/md5calc.hpp"
|
|
#include "../common/mmo.hpp"
|
|
#include "../common/msg_conf.hpp"
|
|
#include "../common/random.hpp"
|
|
#include "../common/showmsg.hpp"
|
|
#include "../common/socket.hpp" //ip2str
|
|
#include "../common/strlib.hpp"
|
|
#include "../common/timer.hpp"
|
|
#include "../common/utils.hpp"
|
|
#include "../config/core.hpp"
|
|
|
|
#include "account.hpp"
|
|
#include "ipban.hpp"
|
|
#include "loginchrif.hpp"
|
|
#include "loginclif.hpp"
|
|
#include "logincnslif.hpp"
|
|
#include "loginlog.hpp"
|
|
|
|
#define LOGIN_MAX_MSG 30 /// Max number predefined in msg_conf
|
|
static char* msg_table[LOGIN_MAX_MSG]; /// Login Server messages_conf
|
|
|
|
//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 database
|
|
AccountDB* accounts = NULL;
|
|
// 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 login_fd; // login server file descriptor socket
|
|
|
|
//early declaration
|
|
bool login_check_password(const char* md5key, int passwdenc, const char* passwd, const char* refpass);
|
|
|
|
///Accessors
|
|
AccountDB* login_get_accounts_db(void){
|
|
return accounts;
|
|
}
|
|
|
|
// 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;
|
|
p->char_server = -1;
|
|
p->waiting_disconnect = INVALID_TIMER;
|
|
return db_ptr2data(p);
|
|
}
|
|
|
|
/**
|
|
* Receive info from char-serv that this user is online
|
|
* This function will start a timer to recheck if that user still online
|
|
* @param char_server : Serv id where account_id is connected
|
|
* @param account_id : aid connected
|
|
* @return the new online_login_data for that user
|
|
*/
|
|
struct online_login_data* login_add_online_user(int char_server, uint32 account_id){
|
|
struct online_login_data* p;
|
|
p = (struct online_login_data*)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, login_waiting_disconnect_timer);
|
|
p->waiting_disconnect = INVALID_TIMER;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* 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 login_remove_online_user(uint32 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, login_waiting_disconnect_timer);
|
|
|
|
idb_remove(online_db, 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
|
|
*/
|
|
TIMER_FUNC(login_waiting_disconnect_timer){
|
|
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 ){
|
|
p->waiting_disconnect = INVALID_TIMER;
|
|
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
|
|
*/
|
|
int login_online_db_setoffline(DBKey key, DBData *data, va_list ap) {
|
|
struct online_login_data* p = (struct online_login_data*)db_data2ptr(data);
|
|
int server = va_arg(ap, int);
|
|
if( server == -1 ) {
|
|
p->char_server = -1;
|
|
if( p->waiting_disconnect != INVALID_TIMER ) {
|
|
delete_timer(p->waiting_disconnect, login_waiting_disconnect_timer);
|
|
p->waiting_disconnect = INVALID_TIMER;
|
|
}
|
|
}
|
|
else if( p->char_server == server )
|
|
p->char_server = -2; //Char server disconnected.
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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 login_online_data_cleanup_sub(DBKey key, DBData *data, va_list ap) {
|
|
struct online_login_data *character= (struct online_login_data*)db_data2ptr(data);
|
|
if (character->char_server == -2) //Unknown server.. set them offline
|
|
login_remove_online_user(character->account_id);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
static TIMER_FUNC(login_online_data_cleanup){
|
|
online_db->foreach(online_db, login_online_data_cleanup_sub);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
struct mmo_account acc;
|
|
|
|
//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 >= login_config.allowed_regs ) {
|
|
ShowNotice("Account registration denied (registration limit exceeded)\n");
|
|
return 3;
|
|
}
|
|
|
|
if( login_config.new_acc_length_limit && ( strlen(userid) < 4 || strlen(pass) < 4 ) )
|
|
return 1;
|
|
|
|
// check for invalid inputs
|
|
if( sex != 'M' && sex != 'F' )
|
|
return 0; // 0 = Unregistered ID
|
|
|
|
// check if the account doesn't exist already
|
|
if( accounts->load_str(accounts, &acc, userid) ) {
|
|
ShowNotice("Attempt of creation of an already existant account (account: %s, sex: %c)\n", userid, sex);
|
|
return 1; // 1 = Incorrect Password
|
|
}
|
|
|
|
memset(&acc, '\0', sizeof(acc));
|
|
acc.account_id = -1; // assigned by account db
|
|
safestrncpy(acc.userid, userid, sizeof(acc.userid));
|
|
safestrncpy(acc.pass, pass, sizeof(acc.pass));
|
|
acc.sex = sex;
|
|
safestrncpy(acc.email, "a@a.com", sizeof(acc.email));
|
|
acc.expiration_time = ( login_config.start_limited_time != -1 ) ? time(NULL) + login_config.start_limited_time : 0;
|
|
safestrncpy(acc.lastlogin, "", sizeof(acc.lastlogin));
|
|
safestrncpy(acc.last_ip, last_ip, sizeof(acc.last_ip));
|
|
safestrncpy(acc.birthdate, "", sizeof(acc.birthdate));
|
|
safestrncpy(acc.pincode, "", sizeof(acc.pincode));
|
|
acc.pincode_change = 0;
|
|
acc.char_slots = MIN_CHARS;
|
|
#ifdef VIP_ENABLE
|
|
acc.vip_time = 0;
|
|
acc.old_group = 0;
|
|
#endif
|
|
if( !accounts->create(accounts, &acc) )
|
|
return 0;
|
|
|
|
ShowNotice("Account creation (account %s, id: %d, sex: %c)\n", acc.userid, acc.account_id, acc.sex);
|
|
|
|
if( DIFF_TICK(tick, new_reg_tick) > 0 ) {// Update the registration check.
|
|
num_regs = 0;
|
|
new_reg_tick = tick + login_config.time_allowed*1000;
|
|
}
|
|
++num_regs;
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
char ip[16];
|
|
ip2str(session[sd->fd]->client_addr, ip);
|
|
|
|
// DNS Blacklist check
|
|
if( login_config.use_dnsbl ) {
|
|
char r_ip[16];
|
|
char ip_dnsbl[256];
|
|
char* dnsbl_serv;
|
|
uint8* sin_addr = (uint8*)&session[sd->fd]->client_addr;
|
|
|
|
sprintf(r_ip, "%u.%u.%u.%u", sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3]);
|
|
|
|
for( dnsbl_serv = strtok(login_config.dnsbl_servs,","); dnsbl_serv != NULL; dnsbl_serv = strtok(NULL,",") ) {
|
|
sprintf(ip_dnsbl, "%s.%s", r_ip, trim(dnsbl_serv));
|
|
if( host2ip(ip_dnsbl) ) {
|
|
ShowInfo("DNSBL: (%s) Blacklisted. User Kicked.\n", r_ip);
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
len = strnlen(sd->userid, NAME_LENGTH);
|
|
|
|
// Account creation with _M/_F
|
|
if( login_config.new_account_flag ) {
|
|
if( len > 2 && strnlen(sd->passwd, NAME_LENGTH) > 0 && // valid user and password lengths
|
|
sd->passwdenc == 0 && // unencoded password
|
|
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 = login_mmo_auth_new(sd->userid, sd->passwd, TOUPPER(sd->userid[len+1]), ip);
|
|
if( result != -1 )
|
|
return result;// Failed to make account. [Skotlex].
|
|
}
|
|
}
|
|
|
|
if( !accounts->load_str(accounts, &acc, sd->userid) ) {
|
|
ShowNotice("Unknown account (account: %s, ip: %s)\n", sd->userid, ip);
|
|
return 0; // 0 = Unregistered ID
|
|
}
|
|
|
|
if( !login_check_password(sd->md5key, sd->passwdenc, sd->passwd, acc.pass) ) {
|
|
ShowNotice("Invalid password (account: '%s', ip: %s)\n", sd->userid, ip);
|
|
return 1; // 1 = Incorrect Password
|
|
}
|
|
|
|
if( acc.expiration_time != 0 && acc.expiration_time < time(NULL) ) {
|
|
ShowNotice("Connection refused (account: %s, expired ID, ip: %s)\n", sd->userid, ip);
|
|
return 2; // 2 = This ID is expired
|
|
}
|
|
|
|
if( acc.unban_time != 0 && acc.unban_time > time(NULL) ) {
|
|
char tmpstr[24];
|
|
timestamp2string(tmpstr, sizeof(tmpstr), acc.unban_time, login_config.date_format);
|
|
ShowNotice("Connection refused (account: %s, banned until %s, ip: %s)\n", sd->userid, tmpstr, ip);
|
|
return 6; // 6 = Your are Prohibited to log in until %s
|
|
}
|
|
|
|
if( acc.state != 0 ) {
|
|
ShowNotice("Connection refused (account: %s, state: %d, ip: %s)\n", sd->userid, acc.state, ip);
|
|
return acc.state - 1;
|
|
}
|
|
|
|
if( login_config.client_hash_check && !isServer ) {
|
|
struct client_hash_node *node = NULL;
|
|
bool match = false;
|
|
|
|
for( node = login_config.client_hash_nodes; node; node = node->next ) {
|
|
if( acc.group_id < node->group_id )
|
|
continue;
|
|
if( *node->hash == '\0' // Allowed to login without hash
|
|
|| (sd->has_client_hash && memcmp(node->hash, sd->client_hash, 16) == 0 ) // Correct hash
|
|
) {
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !match ) {
|
|
char smd5[33];
|
|
int i;
|
|
|
|
if( !sd->has_client_hash ) {
|
|
ShowNotice("Client didn't send client hash (account: %s, ip: %s)\n", sd->userid, acc.state, ip);
|
|
return 5;
|
|
}
|
|
|
|
for( i = 0; i < 16; i++ )
|
|
sprintf(&smd5[i * 2], "%02x", sd->client_hash[i]);
|
|
|
|
ShowNotice("Invalid client hash (account: %s, sent md5: %d, ip: %s)\n", sd->userid, smd5, ip);
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
ShowNotice("Authentication accepted (account: %s, id: %d, ip: %s)\n", sd->userid, acc.account_id, ip);
|
|
|
|
// update session data
|
|
sd->account_id = acc.account_id;
|
|
sd->login_id1 = rnd() + 1;
|
|
sd->login_id2 = rnd() + 1;
|
|
safestrncpy(sd->lastlogin, acc.lastlogin, sizeof(sd->lastlogin));
|
|
sd->sex = acc.sex;
|
|
sd->group_id = acc.group_id;
|
|
|
|
// update account data
|
|
timestamp2string(acc.lastlogin, sizeof(acc.lastlogin), time(NULL), "%Y-%m-%d %H:%M:%S");
|
|
safestrncpy(acc.last_ip, ip, sizeof(acc.last_ip));
|
|
acc.unban_time = 0;
|
|
acc.logincount++;
|
|
|
|
accounts->save(accounts, &acc);
|
|
|
|
if( sd->sex != 'S' && sd->account_id < START_ACCOUNT_NUM )
|
|
ShowWarning("Account %s has account id %d! Account IDs must be over %d to work properly!\n", sd->userid, sd->account_id, START_ACCOUNT_NUM);
|
|
|
|
return -1; // account OK
|
|
}
|
|
|
|
/**
|
|
* 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];
|
|
|
|
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 <passwordencrypt></passwordencrypt>
|
|
// password mode set to 2 -> md5(refpass, md5key) enable with <passwordencrypt2></passwordencrypt2>
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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))
|
|
{
|
|
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 ){
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Reading main configuration file.
|
|
* @param cfgName: Name of the configuration (could be fullpath)
|
|
* @param normal: Config read normally when server started
|
|
* @return True:success, Fals:failure (file not found|readable)
|
|
*/
|
|
bool login_config_read(const char* cfgName, bool normal) {
|
|
char line[1024], w1[32], w2[1024];
|
|
FILE* fp = fopen(cfgName, "r");
|
|
if (fp == NULL) {
|
|
ShowError("Configuration file (%s) not found.\n", cfgName);
|
|
return false;
|
|
}
|
|
while(fgets(line, sizeof(line), fp)) {
|
|
if (line[0] == '/' && line[1] == '/')
|
|
continue;
|
|
|
|
if (sscanf(line, "%31[^:]: %1023[^\r\n]", w1, w2) < 2)
|
|
continue;
|
|
|
|
// Config that loaded only when server started, not by reloading config file
|
|
if (normal) {
|
|
if( !strcmpi(w1, "bind_ip") ) {
|
|
login_config.login_ip = host2ip(w2);
|
|
if( login_config.login_ip ) {
|
|
char ip_str[16];
|
|
ShowStatus("Login server binding IP address : %s -> %s\n", w2, ip2str(login_config.login_ip, ip_str));
|
|
}
|
|
}
|
|
else if( !strcmpi(w1, "login_port") )
|
|
login_config.login_port = (uint16)atoi(w2);
|
|
else if(!strcmpi(w1, "console"))
|
|
login_config.console = (bool)config_switch(w2);
|
|
}
|
|
|
|
if(!strcmpi(w1,"timestamp_format"))
|
|
safestrncpy(timestamp_format, w2, 20);
|
|
else if(strcmpi(w1,"db_path")==0)
|
|
safestrncpy(db_path, w2, ARRAYLENGTH(db_path));
|
|
else if(!strcmpi(w1,"stdout_with_ansisequence"))
|
|
stdout_with_ansisequence = config_switch(w2);
|
|
else if(!strcmpi(w1,"console_silent")) {
|
|
msg_silent = atoi(w2);
|
|
if( msg_silent ) /* only bother if we actually have this enabled */
|
|
ShowInfo("Console Silent Setting: %d\n", atoi(w2));
|
|
}
|
|
else if (strcmpi(w1, "console_msg_log") == 0)
|
|
console_msg_log = atoi(w2);
|
|
else if (strcmpi(w1, "console_log_filepath") == 0)
|
|
safestrncpy(console_log_filepath, w2, sizeof(console_log_filepath));
|
|
else if(!strcmpi(w1, "log_login"))
|
|
login_config.log_login = (bool)config_switch(w2);
|
|
else if(!strcmpi(w1, "new_account"))
|
|
login_config.new_account_flag = (bool)config_switch(w2);
|
|
else if(!strcmpi(w1, "new_acc_length_limit"))
|
|
login_config.new_acc_length_limit = (bool)config_switch(w2);
|
|
else if(!strcmpi(w1, "start_limited_time"))
|
|
login_config.start_limited_time = atoi(w2);
|
|
else if(!strcmpi(w1, "use_MD5_passwords"))
|
|
login_config.use_md5_passwds = (bool)config_switch(w2);
|
|
else if(!strcmpi(w1, "group_id_to_connect"))
|
|
login_config.group_id_to_connect = atoi(w2);
|
|
else if(!strcmpi(w1, "min_group_id_to_connect"))
|
|
login_config.min_group_id_to_connect = atoi(w2);
|
|
else if(!strcmpi(w1, "date_format"))
|
|
safestrncpy(login_config.date_format, w2, sizeof(login_config.date_format));
|
|
else if(!strcmpi(w1, "allowed_regs")) //account flood protection system
|
|
login_config.allowed_regs = atoi(w2);
|
|
else if(!strcmpi(w1, "time_allowed"))
|
|
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"))
|
|
safestrncpy(login_config.dnsbl_servs, w2, sizeof(login_config.dnsbl_servs));
|
|
else if(!strcmpi(w1, "ipban_cleanup_interval"))
|
|
login_config.ipban_cleanup_interval = (unsigned int)atoi(w2);
|
|
else if(!strcmpi(w1, "ip_sync_interval"))
|
|
login_config.ip_sync_interval = (unsigned int)1000*60*atoi(w2); //w2 comes in minutes.
|
|
else if(!strcmpi(w1, "client_hash_check"))
|
|
login_config.client_hash_check = config_switch(w2);
|
|
else if(!strcmpi(w1, "client_hash")) {
|
|
int group = 0;
|
|
char md5[33];
|
|
|
|
if (sscanf(w2, "%3d, %32s", &group, md5) == 2) {
|
|
struct client_hash_node *nnode;
|
|
CREATE(nnode, struct client_hash_node, 1);
|
|
if (strcmpi(md5, "disabled") == 0) {
|
|
nnode->hash[0] = '\0';
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < 32; i += 2) {
|
|
char buf[3];
|
|
unsigned int byte;
|
|
|
|
memcpy(buf, &md5[i], 2);
|
|
buf[2] = 0;
|
|
|
|
sscanf(buf, "%2x", &byte);
|
|
nnode->hash[i / 2] = (uint8)(byte & 0xFF);
|
|
}
|
|
}
|
|
nnode->group_id = group;
|
|
nnode->next = login_config.client_hash_nodes;
|
|
login_config.client_hash_nodes = nnode;
|
|
}
|
|
} else if(strcmpi(w1, "chars_per_account") == 0) { //maxchars per account [Sirius]
|
|
login_config.char_per_account = atoi(w2);
|
|
if( login_config.char_per_account <= 0 || login_config.char_per_account > MAX_CHARS ) {
|
|
if( login_config.char_per_account > MAX_CHARS ) {
|
|
ShowWarning("Max chars per account '%d' exceeded limit. Defaulting to '%d'.\n", login_config.char_per_account, MAX_CHARS);
|
|
login_config.char_per_account = MAX_CHARS;
|
|
}
|
|
login_config.char_per_account = MIN_CHARS;
|
|
}
|
|
}
|
|
#ifdef VIP_ENABLE
|
|
else if(strcmpi(w1,"vip_group")==0)
|
|
login_config.vip_sys.group = cap_value(atoi(w2),0,99);
|
|
else if(strcmpi(w1,"vip_char_increase")==0) {
|
|
if (atoi(w2) == -1)
|
|
login_config.vip_sys.char_increase = MAX_CHAR_VIP;
|
|
else
|
|
login_config.vip_sys.char_increase = atoi(w2);
|
|
if (login_config.vip_sys.char_increase > (unsigned int) MAX_CHARS-login_config.char_per_account) {
|
|
ShowWarning("vip_char_increase too high, can only go up to %d, according to your char_per_account config %d\n",
|
|
MAX_CHARS-login_config.char_per_account,login_config.char_per_account);
|
|
login_config.vip_sys.char_increase = MAX_CHARS-login_config.char_per_account;
|
|
}
|
|
}
|
|
#endif
|
|
else if(!strcmpi(w1, "import"))
|
|
login_config_read(w2, normal);
|
|
else {// try the account engines
|
|
if (!normal)
|
|
continue;
|
|
if (accounts && accounts->set_property(accounts, w1, w2))
|
|
continue;
|
|
// try others
|
|
ipban_config_read(w1, w2);
|
|
loginlog_config_read(w1, w2);
|
|
}
|
|
}
|
|
fclose(fp);
|
|
ShowInfo("Finished reading %s.\n", cfgName);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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.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));
|
|
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));
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Constructor destructor and signal handlers
|
|
|
|
/**
|
|
* Login-serv destructor
|
|
* dealloc..., function called at exit of the login-serv
|
|
*/
|
|
void do_final(void) {
|
|
struct client_hash_node *hn = login_config.client_hash_nodes;
|
|
AccountDB* db = accounts;
|
|
|
|
while (hn)
|
|
{
|
|
struct client_hash_node *tmp = hn;
|
|
hn = hn->next;
|
|
aFree(tmp);
|
|
}
|
|
|
|
login_log(0, "login server", 100, "login server shutdown");
|
|
ShowStatus("Terminating...\n");
|
|
|
|
if( login_config.log_login )
|
|
loginlog_final();
|
|
|
|
do_final_msg();
|
|
ipban_final();
|
|
do_final_loginclif();
|
|
do_final_logincnslif();
|
|
|
|
if (db) { // destroy account engine
|
|
db->destroy(db);
|
|
db = NULL;
|
|
}
|
|
|
|
accounts = NULL; // destroyed in account_engine
|
|
online_db->destroy(online_db, NULL);
|
|
auth_db->destroy(auth_db, NULL);
|
|
|
|
do_final_loginchrif();
|
|
|
|
if( login_fd != -1 )
|
|
{
|
|
do_close(login_fd);
|
|
login_fd = -1;
|
|
}
|
|
|
|
ShowStatus("Finished.\n");
|
|
}
|
|
|
|
/**
|
|
* 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]
|
|
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) {
|
|
}
|
|
|
|
// 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) {
|
|
runflag = LOGINSERVER_ST_STARTING;
|
|
|
|
// Init default value
|
|
safestrncpy(console_log_filepath, "./log/login-msg_log.log", sizeof(console_log_filepath));
|
|
|
|
// initialize engine
|
|
accounts = account_db_sql();
|
|
|
|
// read login-server configuration
|
|
login_set_defaults();
|
|
logcnslif_get_options(argc,argv);
|
|
|
|
login_config_read(login_config.loginconf_name, true);
|
|
msg_config_read(login_config.msgconf_name);
|
|
login_lan_config_read(login_config.lanconf_name);
|
|
//end config
|
|
|
|
rnd_init();
|
|
|
|
do_init_loginclif();
|
|
do_init_loginchrif();
|
|
|
|
// initialize logging
|
|
if( login_config.log_login )
|
|
loginlog_init();
|
|
|
|
// initialize static and dynamic ipban system
|
|
ipban_init();
|
|
|
|
// Online user database init
|
|
online_db = idb_alloc(DB_OPT_RELEASE_DATA);
|
|
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(logclif_parse);
|
|
|
|
// every 10 minutes cleanup online account db.
|
|
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
|
|
if( accounts == NULL ) {
|
|
ShowFatalError("do_init: account engine not found.\n");
|
|
exit(EXIT_FAILURE);
|
|
} else {
|
|
if(!accounts->init(accounts)) {
|
|
ShowFatalError("do_init: Failed to initialize account engine.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
// server port open & binding
|
|
if( (login_fd = make_listen_bind(login_config.login_ip,login_config.login_port)) == -1 ) {
|
|
ShowFatalError("Failed to bind to port '" CL_WHITE "%d" CL_RESET "'\n",login_config.login_port);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if( runflag != CORE_ST_STOP ) {
|
|
shutdown_callback = do_shutdown;
|
|
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");
|
|
|
|
return 0;
|
|
}
|