/**
* @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"
#include "../common/malloc.h"
#include "../common/md5calc.h"
#include "../common/random.h"
#include "../common/showmsg.h"
#include "../common/socket.h" //ip2str
#include "../common/strlib.h"
#include "../common/timer.h"
#include "../common/msg_conf.h"
#include "../common/cli.h"
#include "../common/utils.h"
#include "../common/mmo.h"
#include "../config/core.h"
#include "account.h"
#include "ipban.h"
#include "login.h"
#include "loginlog.h"
#include "loginclif.h"
#include "loginchrif.h"
#include "logincnslif.h"
#include <stdlib.h>
#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 = 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
*/
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 ){
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 = 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= 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 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;
}
/**
* 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_%c, pass: %s, received pass: %s)\n", userid, sex, acc.pass, pass);
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, "[email protected]", sizeof(acc.email));
acc.expiration_time = ( login_config.start_limited_time != -1 ) ? time(NULL) + login_config.start_limited_time : 0;
safestrncpy(acc.lastlogin, "0000-00-00 00:00:00", sizeof(acc.lastlogin));
safestrncpy(acc.last_ip, last_ip, sizeof(acc.last_ip));
safestrncpy(acc.birthdate, "0000-00-00", 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, pass: %s, sex: %c)\n", acc.userid, acc.account_id, acc.pass, 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;
}
}
}
//Client Version check
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);
// 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, received pass: %s, ip: %s)\n", sd->userid, sd->passwd, ip);
return 0; // 0 = Unregistered ID
}
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
}
if( acc.expiration_time != 0 && acc.expiration_time < time(NULL) ) {
ShowNotice("Connection refused (account: %s, pass: %s, expired ID, ip: %s)\n", sd->userid, sd->passwd, 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, pass: %s, banned until %s, ip: %s)\n", sd->userid, sd->passwd, tmpstr, ip);
return 6; // 6 = Your are Prohibited to log in until %s
}
if( acc.state != 0 ) {
ShowNotice("Connection refused (account: %s, pass: %s, state: %d, ip: %s)\n", sd->userid, sd->passwd, 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, pass: %s, ip: %s)\n", sd->userid, sd->passwd, 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, pass: %s, sent md5: %d, ip: %s)\n", sd->userid, sd->passwd, 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, "check_client_version"))
login_config.check_client_version = (bool)config_switch(w2);
else if(!strcmpi(w1, "client_version_to_connect"))
login_config.client_version_to_connect = strtoul(w2, NULL, 10);
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(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 = cap_value(atoi(w2),0,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.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);
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;
}
Login.h
/**
* @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 {
LOGINSERVER_ST_RUNNING = CORE_ST_LAST,
LOGINSERVER_ST_STARTING,
LOGINSERVER_ST_SHUTDOWN,
LOGINSERVER_ST_LAST
};
/// supported encryption types: 1- passwordencrypt, 2- passwordencrypt2, 3- both
#define PASSWORDENC 3
///Struct of 1 client connected to login-serv
struct login_session_data {
uint32 account_id; ///also GID
long login_id1;
long login_id2;
char sex; /// 'F','M','S'
char userid[NAME_LENGTH]; /// account name
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
char lastlogin[24]; ///date when last logged, Y-M-D HH:MM:SS
uint8 group_id; ///groupid of account
uint8 clienttype; /// ???
uint32 version; ///version contained in clientinfo
uint8 client_hash[16]; ///hash of client
int has_client_hash; ///client ha sent an hash
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
int fd; ///char-serv socket (well actually file descriptor)
uint32 ip; ///char-serv IP
uint16 port; ///char-serv rt
uint16 users; /// user count on this 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; //inferior or egal group to apply restriction
uint8 hash[16]; ///hash required for that groupid or below
struct client_hash_node *next; ///next entry
};
struct Login_Config {
uint32 login_ip; /// the address to bind to
uint16 login_port; /// the port to bind to
unsigned int ipban_cleanup_interval; /// interval (in seconds) to clean up expired IP bans
unsigned int ip_sync_interval; /// interval (in minutes) to execute a DNS/IP update (for dynamic IPs)
bool log_login; /// whether to log login server actions or not
char date_format[32]; /// date format used in messages
bool console; /// console input system enabled?
bool new_account_flag,new_acc_length_limit; /// autoregistration via _M/_F ? / if yes minimum length is 4?
int start_limited_time; /// new account expiration time (-1: unlimited)
bool use_md5_passwds; /// work with password hashes instead of plaintext passwords?
int group_id_to_connect; /// required group id to connect
int min_group_id_to_connect; /// minimum group id to connect
bool check_client_version; /// check the clientversion set in the clientinfo ?
uint32 client_version_to_connect; /// the client version needed to connect (if checking is enabled)
bool ipban; /// perform IP blocking (via contents of `ipbanlist`) ?
bool dynamic_pass_failure_ban; /// automatic IP blocking due to failed login attempts ?
unsigned int dynamic_pass_failure_ban_interval; /// how far to scan the loginlog for password failures in minutes
unsigned int dynamic_pass_failure_ban_limit; /// number of failures needed to trigger the ipban
unsigned int dynamic_pass_failure_ban_duration; /// duration of the ipban in minutes
bool use_dnsbl; /// dns blacklist blocking ?
char dnsbl_servs[1024]; /// comma-separated list of dnsbl servers
int allowed_regs; /// max number of registration
int time_allowed; /// registration interval in seconds
int client_hash_check; /// flags for checking client md5
struct client_hash_node *client_hash_nodes; /// linked list containing 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 {
unsigned int group; /// VIP group ID
unsigned int char_increase; /// number of char-slot to increase in VIP state
} 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 )
#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);
bool login_config_read(const char* cfgName, bool normal);
/// Online User Database [Wizputer]
struct online_login_data {
uint32 account_id;
int waiting_disconnect;
int char_server;
};
extern DBMap* online_db; // uint32 account_id -> struct online_login_data*
/// Auth database
#define AUTH_TIMEOUT 30000
struct auth_node {
uint32 account_id;
uint32 login_id1;
uint32 login_id2;
uint32 ip;
char sex;
uint32 version;
uint8 clienttype;
};
extern DBMap* auth_db; // uint32 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, uint32 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(uint32 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_ */
Question
PrntScrn
Hello everybody, I'm here asking for help to apply the patch.diff the InternalGuard in src.
Diff
My src
Login.c
Login.h
Link to comment
Share on other sites
2 answers to this question
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.