summaryrefslogtreecommitdiff
path: root/src/login
diff options
context:
space:
mode:
Diffstat (limited to 'src/login')
-rw-r--r--src/login/Makefile.in63
-rw-r--r--src/login/account.h159
-rw-r--r--src/login/account_sql.c578
-rw-r--r--src/login/account_txt.c622
-rw-r--r--src/login/admin.c606
-rw-r--r--src/login/ipban.h25
-rw-r--r--src/login/ipban_sql.c209
-rw-r--r--src/login/ipban_txt.c50
-rw-r--r--src/login/login.c2033
-rw-r--r--src/login/login.h43
-rw-r--r--src/login/loginlog.h14
-rw-r--r--src/login/loginlog_sql.c93
-rw-r--r--src/login/loginlog_txt.c74
13 files changed, 2904 insertions, 1665 deletions
diff --git a/src/login/Makefile.in b/src/login/Makefile.in
index aeb41b667..1f6080746 100644
--- a/src/login/Makefile.in
+++ b/src/login/Makefile.in
@@ -10,36 +10,71 @@ COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h
../common/grfio.h ../common/mapindex.h \
../common/ers.h ../common/md5calc.h
-LOGIN_OBJ = obj_txt/login.o obj_txt/admin.o
-LOGIN_H = login.h
+COMMON_SQL_OBJ = ../common/obj_sql/sql.o
+COMMON_SQL_H = ../common/sql.h
+
+LOGIN_OBJ = login.o admin.o
+LOGIN_TXT_OBJ = $(LOGIN_OBJ:%=obj_txt/%) \
+ obj_txt/account_txt.o obj_txt/ipban_txt.o obj_txt/loginlog_txt.o
+LOGIN_SQL_OBJ = $(LOGIN_OBJ:%=obj_sql/%) \
+ obj_sql/account_sql.o obj_sql/ipban_sql.o obj_sql/loginlog_sql.o
+LOGIN_H = login.h account.h ipban.h loginlog.h
+
+HAVE_MYSQL=@HAVE_MYSQL@
+ifeq ($(HAVE_MYSQL),yes)
+ LOGIN_SERVER_SQL_DEPENDS=obj_sql $(LOGIN_SQL_OBJ) $(COMMON_OBJ) $(COMMON_SQL_OBJ)
+else
+ LOGIN_SERVER_SQL_DEPENDS=needs_mysql
+endif
@SET_MAKE@
#####################################################################
-.PHONY : all login-server clean help
+.PHONY :all txt sql clean help
-all: login-server
+all: txt sql
-login-server: obj_txt $(LOGIN_OBJ) $(COMMON_OBJ)
- @CC@ @LDFLAGS@ -o ../../login-server@EXEEXT@ $(LOGIN_OBJ) $(COMMON_OBJ) @LIBS@
+txt: obj_txt login-server
+
+sql: obj_sql login-server_sql
clean:
- rm -rf *.o obj_txt ../../login-server@EXEEXT@
+ rm -rf *.o obj_txt obj_sql ../../login-server@EXEEXT@ ../../login-server_sql@EXEEXT@
help:
- @echo "possible targets are 'login-server' 'all' 'clean' 'help'"
- @echo "'login-server' - login server (TXT version)"
- @echo "'all' - builds all above targets"
- @echo "'clean' - cleans builds and objects"
- @echo "'help' - outputs this message"
+ @echo "possible targets are 'sql' 'txt' 'all' 'clean' 'help'"
+ @echo "'sql' - login server (SQL version)"
+ @echo "'txt' - login server (TXT version)"
+ @echo "'all' - builds all above targets"
+ @echo "'clean' - cleans builds and objects"
+ @echo "'help' - outputs this message"
#####################################################################
+needs_mysql:
+ @echo "MySQL not found or disabled by the configure script"
+ @exit 1
+
+# object directories
obj_txt:
- -mkdir obj_txt
+ test -d obj_txt || mkdir obj_txt
+obj_sql:
+ test -d obj_sql || mkdir obj_sql
+
+#executables
+login-server: $(LOGIN_TXT_OBJ) $(COMMON_OBJ)
+ @CC@ @LDFLAGS@ -o ../../login-server@EXEEXT@ $(LOGIN_TXT_OBJ) $(COMMON_OBJ) @LIBS@
+
+login-server_sql: $(LOGIN_SERVER_SQL_DEPENDS)
+ @CC@ @LDFLAGS@ -o ../../login-server_sql@EXEEXT@ $(LOGIN_SQL_OBJ) $(COMMON_OBJ) $(COMMON_SQL_OBJ) @LIBS@ @MYSQL_LIBS@
+
+# login object files
obj_txt/%.o: %.c $(LOGIN_H) $(COMMON_H)
- @CC@ @CFLAGS@ $(CUSTOM_CFLAGS) -DTXT_ONLY @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
+ @CC@ @CFLAGS@ $(CUSTOM_CFLAGS) -DWITH_TXT @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
+
+obj_sql/%.o: %.c $(LOGIN_H) $(COMMON_H)
+ @CC@ @CFLAGS@ $(CUSTOM_CFLAGS) -DWITH_SQL @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $<
# missing common object files
../common/obj_all/%.o:
diff --git a/src/login/account.h b/src/login/account.h
new file mode 100644
index 000000000..4cc9b353b
--- /dev/null
+++ b/src/login/account.h
@@ -0,0 +1,159 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#ifndef __ACCOUNT_H_INCLUDED__
+#define __ACCOUNT_H_INCLUDED__
+
+#include "../common/cbasetypes.h"
+#include "../common/mmo.h" // ACCOUNT_REG2_NUM
+
+typedef struct AccountDB AccountDB;
+typedef struct AccountDBIterator AccountDBIterator;
+
+
+// standard engines
+#ifdef WITH_TXT
+AccountDB* account_db_txt(void);
+#endif
+#ifdef WITH_SQL
+AccountDB* account_db_sql(void);
+#endif
+// extra engines (will probably use the other txt functions)
+#define ACCOUNTDB_CONSTRUCTOR_(engine) account_db_##engine
+#define ACCOUNTDB_CONSTRUCTOR(engine) ACCOUNTDB_CONSTRUCTOR_(engine)
+#ifdef ACCOUNTDB_ENGINE_0
+AccountDB* ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_0)(void);
+#endif
+#ifdef ACCOUNTDB_ENGINE_1
+AccountDB* ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_1)(void);
+#endif
+#ifdef ACCOUNTDB_ENGINE_2
+AccountDB* ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_2)(void);
+#endif
+#ifdef ACCOUNTDB_ENGINE_3
+AccountDB* ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_3)(void);
+#endif
+#ifdef ACCOUNTDB_ENGINE_4
+AccountDB* ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_4)(void);
+#endif
+
+
+struct mmo_account
+{
+ int account_id;
+ char userid[24];
+ char pass[32+1]; // 23+1 for plaintext, 32+1 for md5-ed passwords
+ char sex; // gender (M/F/S)
+ char email[40]; // e-mail (by default: a@a.com)
+ int level; // GM level
+ unsigned int state; // packet 0x006a value + 1 (0: compte OK)
+ time_t unban_time; // (timestamp): ban time limit of the account (0 = no ban)
+ time_t expiration_time; // (timestamp): validity limit of the account (0 = unlimited)
+ unsigned int logincount;// number of successful auth attempts
+ char lastlogin[24]; // date+time of last successful login
+ char last_ip[16]; // save of last IP of connection
+ int account_reg2_num;
+ struct global_reg account_reg2[ACCOUNT_REG2_NUM]; // account script variables (stored on login server)
+};
+
+
+struct AccountDBIterator
+{
+ /// Destroys this iterator, releasing all allocated memory (including itself).
+ ///
+ /// @param self Iterator
+ void (*destroy)(AccountDBIterator* self);
+
+ /// Fetches the next account in the database.
+ /// Fills acc with the account data.
+ /// @param self Iterator
+ /// @param acc Account data
+ /// @return true if successful
+ bool (*next)(AccountDBIterator* self, struct mmo_account* acc);
+};
+
+
+struct AccountDB
+{
+ /// Initializes this database, making it ready for use.
+ /// Call this after setting the properties.
+ ///
+ /// @param self Database
+ /// @return true if successful
+ bool (*init)(AccountDB* self);
+
+ /// Destroys this database, releasing all allocated memory (including itself).
+ ///
+ /// @param self Database
+ void (*destroy)(AccountDB* self);
+
+ /// Gets a property from this database.
+ /// These read-only properties must be implemented:
+ /// "engine.name" -> "txt", "sql", ...
+ /// "engine.version" -> internal version
+ /// "engine.comment" -> anything (suggestion: description or specs of the engine)
+ ///
+ /// @param self Database
+ /// @param key Property name
+ /// @param buf Buffer for the value
+ /// @param buflen Buffer length
+ /// @return true if successful
+ bool (*get_property)(AccountDB* self, const char* key, char* buf, size_t buflen);
+
+ /// Sets a property in this database.
+ ///
+ /// @param self Database
+ /// @param key Property name
+ /// @param value Property value
+ /// @return true if successful
+ bool (*set_property)(AccountDB* self, const char* key, const char* value);
+
+ /// Creates a new account in this database.
+ /// If acc->account_id is not -1, the provided value will be used.
+ /// Otherwise the account_id will be auto-generated and written to acc->account_id.
+ ///
+ /// @param self Database
+ /// @param acc Account data
+ /// @return true if successful
+ bool (*create)(AccountDB* self, struct mmo_account* acc);
+
+ /// Removes an account from this database.
+ ///
+ /// @param self Database
+ /// @param account_id Account id
+ /// @return true if successful
+ bool (*remove)(AccountDB* self, const int account_id);
+
+ /// Modifies the data of an existing account.
+ /// Uses acc->account_id to identify the account.
+ ///
+ /// @param self Database
+ /// @param acc Account data
+ /// @return true if successful
+ bool (*save)(AccountDB* self, const struct mmo_account* acc);
+
+ /// Finds an account with account_id and copies it to acc.
+ ///
+ /// @param self Database
+ /// @param acc Pointer that receives the account data
+ /// @param account_id Target account id
+ /// @return true if successful
+ bool (*load_num)(AccountDB* self, struct mmo_account* acc, const int account_id);
+
+ /// Finds an account with userid and copies it to acc.
+ ///
+ /// @param self Database
+ /// @param acc Pointer that receives the account data
+ /// @param userid Target username
+ /// @return true if successful
+ bool (*load_str)(AccountDB* self, struct mmo_account* acc, const char* userid);
+
+ /// Returns a new forward iterator.
+ ///
+ /// @param self Database
+ /// @return Iterator
+ AccountDBIterator* (*iterator)(AccountDB* self);
+};
+
+
+#endif // __ACCOUNT_H_INCLUDED__
diff --git a/src/login/account_sql.c b/src/login/account_sql.c
new file mode 100644
index 000000000..b510949fa
--- /dev/null
+++ b/src/login/account_sql.c
@@ -0,0 +1,578 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/malloc.h"
+#include "../common/mmo.h"
+#include "../common/showmsg.h"
+#include "../common/sql.h"
+#include "../common/strlib.h"
+#include "../common/timer.h"
+#include "account.h"
+#include <stdlib.h>
+#include <string.h>
+
+/// global defines
+#define ACCOUNT_SQL_DB_VERSION 20080417
+
+/// internal structure
+typedef struct AccountDB_SQL
+{
+ AccountDB vtable; // public interface
+
+ Sql* accounts; // SQL accounts storage
+
+ char db_hostname[32];
+ uint16 db_port;
+ char db_username[32];
+ char db_password[32];
+ char db_database[32];
+ char codepage[32];
+ bool case_sensitive;
+ char account_db[32];
+ char accreg_db[32];
+
+} AccountDB_SQL;
+
+/// internal structure
+typedef struct AccountDBIterator_SQL
+{
+ AccountDBIterator vtable; // public interface
+
+ AccountDB_SQL* db;
+ int last_account_id;
+} AccountDBIterator_SQL;
+
+/// internal functions
+static bool account_db_sql_init(AccountDB* self);
+static void account_db_sql_destroy(AccountDB* self);
+static bool account_db_sql_get_property(AccountDB* self, const char* key, char* buf, size_t buflen);
+static bool account_db_sql_set_property(AccountDB* self, const char* option, const char* value);
+static bool account_db_sql_create(AccountDB* self, struct mmo_account* acc);
+static bool account_db_sql_remove(AccountDB* self, const int account_id);
+static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc);
+static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const int account_id);
+static bool account_db_sql_load_str(AccountDB* self, struct mmo_account* acc, const char* userid);
+static AccountDBIterator* account_db_sql_iterator(AccountDB* self);
+static void account_db_sql_iter_destroy(AccountDBIterator* self);
+static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account* acc);
+
+static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, int account_id);
+static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new);
+
+/// public constructor
+AccountDB* account_db_sql(void)
+{
+ AccountDB_SQL* db = (AccountDB_SQL*)aCalloc(1, sizeof(AccountDB_SQL));
+
+ // set up the vtable
+ db->vtable.init = &account_db_sql_init;
+ db->vtable.destroy = &account_db_sql_destroy;
+ db->vtable.get_property = &account_db_sql_get_property;
+ db->vtable.set_property = &account_db_sql_set_property;
+ db->vtable.save = &account_db_sql_save;
+ db->vtable.create = &account_db_sql_create;
+ db->vtable.remove = &account_db_sql_remove;
+ db->vtable.load_num = &account_db_sql_load_num;
+ db->vtable.load_str = &account_db_sql_load_str;
+ db->vtable.iterator = &account_db_sql_iterator;
+
+ // initialize to default values
+ db->accounts = NULL;
+ safestrncpy(db->db_hostname, "127.0.0.1", sizeof(db->db_hostname));
+ db->db_port = 3306;
+ safestrncpy(db->db_username, "ragnarok", sizeof(db->db_username));
+ safestrncpy(db->db_password, "ragnarok", sizeof(db->db_password));
+ safestrncpy(db->db_database, "ragnarok", sizeof(db->db_database));
+ safestrncpy(db->codepage, "", sizeof(db->codepage));
+ db->case_sensitive = false;
+ safestrncpy(db->account_db, "login", sizeof(db->account_db));
+ safestrncpy(db->accreg_db, "global_reg_value", sizeof(db->accreg_db));
+
+ return &db->vtable;
+}
+
+
+/* ------------------------------------------------------------------------- */
+
+
+/// establishes database connection
+static bool account_db_sql_init(AccountDB* self)
+{
+ AccountDB_SQL* db = (AccountDB_SQL*)self;
+ Sql* sql_handle;
+
+ db->accounts = Sql_Malloc();
+ sql_handle = db->accounts;
+
+ if( SQL_ERROR == Sql_Connect(sql_handle, db->db_username, db->db_password, db->db_hostname, db->db_port, db->db_database) )
+ {
+ Sql_ShowDebug(sql_handle);
+ Sql_Free(db->accounts);
+ db->accounts = NULL;
+ return false;
+ }
+
+ if( db->codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, db->codepage) )
+ Sql_ShowDebug(sql_handle);
+
+ return true;
+}
+
+/// disconnects from database
+static void account_db_sql_destroy(AccountDB* self)
+{
+ AccountDB_SQL* db = (AccountDB_SQL*)self;
+
+ Sql_Free(db->accounts);
+ db->accounts = NULL;
+}
+
+/// Gets a property from this database.
+static bool account_db_sql_get_property(AccountDB* self, const char* key, char* buf, size_t buflen)
+{
+ AccountDB_SQL* db = (AccountDB_SQL*)self;
+ const char* signature = "account.sql.";
+
+ if( strcmp(key, "engine.name") == 0 )
+ {
+ safesnprintf(buf, buflen, "sql");
+ return true;
+ }
+ if( strcmp(key, "engine.version") == 0 )
+ {
+ safesnprintf(buf, buflen, "%d", ACCOUNT_SQL_DB_VERSION);
+ return true;
+ }
+ if( strcmp(key, "engine.comment") == 0 )
+ {
+ safesnprintf(buf, buflen, "SQL Account Database");
+ return true;
+ }
+
+ if( strncmp(key, signature, strlen(signature)) != 0 )
+ return false;
+
+ key += strlen(signature);
+
+ if( strcmpi(key, "db_hostname") == 0 )
+ safesnprintf(buf, buflen, "%s", db->db_hostname);
+ else
+ if( strcmpi(key, "db_port") == 0 )
+ safesnprintf(buf, buflen, "%d", db->db_port);
+ else
+ if( strcmpi(key, "db_username") == 0 )
+ safesnprintf(buf, buflen, "%s", db->db_username);
+ else
+ if( strcmpi(key, "db_password") == 0 )
+ safesnprintf(buf, buflen, "%s", db->db_password);
+ else
+ if( strcmpi(key, "db_database") == 0 )
+ safesnprintf(buf, buflen, "%s", db->db_database);
+ else
+ if( strcmpi(key, "codepage") == 0 )
+ safesnprintf(buf, buflen, "%s", db->codepage);
+ else
+ if( strcmpi(key, "case_sensitive") == 0 )
+ safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0));
+ else
+ if( strcmpi(key, "account_db") == 0 )
+ safesnprintf(buf, buflen, "%s", db->account_db);
+ else
+ if( strcmpi(key, "accreg_db") == 0 )
+ safesnprintf(buf, buflen, "%s", db->accreg_db);
+ else
+ return false;// not found
+
+ return true;
+}
+
+/// if the option is supported, adjusts the internal state
+static bool account_db_sql_set_property(AccountDB* self, const char* key, const char* value)
+{
+ AccountDB_SQL* db = (AccountDB_SQL*)self;
+ const char* signature = "account.sql.";
+
+ if( strncmp(key, signature, strlen(signature)) != 0 )
+ return false;
+
+ key += strlen(signature);
+
+ if( strcmpi(key, "db_hostname") == 0 )
+ safestrncpy(db->db_hostname, value, sizeof(db->db_hostname));
+ else
+ if( strcmpi(key, "db_port") == 0 )
+ db->db_port = (uint16)strtoul(value, NULL, 10);
+ else
+ if( strcmpi(key, "db_username") == 0 )
+ safestrncpy(db->db_username, value, sizeof(db->db_username));
+ else
+ if( strcmpi(key, "db_password") == 0 )
+ safestrncpy(db->db_password, value, sizeof(db->db_password));
+ else
+ if( strcmpi(key, "db_database") == 0 )
+ safestrncpy(db->db_database, value, sizeof(db->db_database));
+ else
+ if( strcmpi(key, "codepage") == 0 )
+ safestrncpy(db->codepage, value, sizeof(db->codepage));
+ else
+ if( strcmpi(key, "case_sensitive") == 0 )
+ db->case_sensitive = config_switch(value);
+ else
+ if( strcmpi(key, "account_db") == 0 )
+ safestrncpy(db->account_db, value, sizeof(db->account_db));
+ else
+ if( strcmpi(key, "accreg_db") == 0 )
+ safestrncpy(db->accreg_db, value, sizeof(db->accreg_db));
+ else // no match
+ return false;
+
+ return true;
+}
+
+/// 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)
+{
+ AccountDB_SQL* db = (AccountDB_SQL*)self;
+ Sql* sql_handle = db->accounts;
+
+ // decide on the account id to assign
+ int account_id;
+ if( acc->account_id != -1 )
+ {// caller specifies it manually
+ account_id = acc->account_id;
+ }
+ else
+ {// ask the database
+ char* data;
+ size_t len;
+
+ if( SQL_SUCCESS != Sql_Query(sql_handle, "SELECT MAX(`account_id`)+1 FROM `%s`", db->account_db) )
+ {
+ Sql_ShowDebug(sql_handle);
+ return false;
+ }
+ if( SQL_SUCCESS != Sql_NextRow(sql_handle) )
+ {
+ Sql_ShowDebug(sql_handle);
+ Sql_FreeResult(sql_handle);
+ return false;
+ }
+
+ Sql_GetData(sql_handle, 0, &data, &len);
+ account_id = ( data != NULL ) ? atoi(data) : 0;
+ Sql_FreeResult(sql_handle);
+
+ if( account_id < START_ACCOUNT_NUM )
+ account_id = START_ACCOUNT_NUM;
+
+ }
+
+ // zero value is prohibited
+ if( account_id == 0 )
+ return false;
+
+ // absolute maximum
+ if( account_id > END_ACCOUNT_NUM )
+ return false;
+
+ // insert the data into the database
+ acc->account_id = account_id;
+ 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)
+{
+ AccountDB_SQL* db = (AccountDB_SQL*)self;
+ Sql* sql_handle = db->accounts;
+ bool result = false;
+
+ if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION")
+ || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->account_db, account_id)
+ || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->accreg_db, account_id) )
+ Sql_ShowDebug(sql_handle);
+ else
+ result = true;
+
+ result &= ( SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") );
+
+ 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)
+{
+ 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)
+{
+ 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)
+{
+ AccountDB_SQL* db = (AccountDB_SQL*)self;
+ Sql* sql_handle = db->accounts;
+ char esc_userid[2*NAME_LENGTH+1];
+ int account_id;
+ char* data;
+
+ Sql_EscapeString(sql_handle, esc_userid, userid);
+
+ // get the list of account IDs for this user ID
+ if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `userid`= %s '%s'",
+ db->account_db, (db->case_sensitive ? "BINARY" : ""), esc_userid) )
+ {
+ Sql_ShowDebug(sql_handle);
+ return false;
+ }
+
+ if( Sql_NumRows(sql_handle) > 1 )
+ {// serious problem - duplicit account
+ ShowError("account_db_sql_load_str: multiple accounts found when retrieving data for account '%s'!\n", userid);
+ Sql_FreeResult(sql_handle);
+ return false;
+ }
+
+ if( SQL_SUCCESS != Sql_NextRow(sql_handle) )
+ {// no such entry
+ Sql_FreeResult(sql_handle);
+ return false;
+ }
+
+ Sql_GetData(sql_handle, 0, &data, NULL);
+ account_id = atoi(data);
+
+ return account_db_sql_load_num(self, acc, account_id);
+}
+
+
+/// Returns a new forward 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));
+
+ // set up the vtable
+ iter->vtable.destroy = &account_db_sql_iter_destroy;
+ iter->vtable.next = &account_db_sql_iter_next;
+
+ // fill data
+ iter->db = db;
+ iter->last_account_id = -1;
+
+ return &iter->vtable;
+}
+
+
+/// Destroys this iterator, releasing all allocated memory (including itself).
+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)
+{
+ AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self;
+ AccountDB_SQL* db = (AccountDB_SQL*)iter->db;
+ Sql* sql_handle = db->accounts;
+ int account_id;
+ char* data;
+
+ // get next account ID
+ if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `account_id` > '%d' ORDER BY `account_id` ASC LIMIT 1",
+ db->account_db, iter->last_account_id) )
+ {
+ Sql_ShowDebug(sql_handle);
+ return false;
+ }
+
+ if( SQL_SUCCESS == Sql_NextRow(sql_handle) &&
+ SQL_SUCCESS == Sql_GetData(sql_handle, 0, &data, NULL) &&
+ data != NULL )
+ {// get account data
+ account_id = atoi(data);
+ if( mmo_auth_fromsql(db, acc, account_id) )
+ {
+ iter->last_account_id = account_id;
+ Sql_FreeResult(sql_handle);
+ return true;
+ }
+ }
+ Sql_FreeResult(sql_handle);
+ return false;
+}
+
+
+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;
+
+ // retrieve login entry for the specified account
+ if( SQL_ERROR == Sql_Query(sql_handle,
+ "SELECT `account_id`,`userid`,`user_pass`,`sex`,`email`,`level`,`state`,`unban_time`,`expiration_time`,`logincount`,`lastlogin`,`last_ip` FROM `%s` WHERE `account_id` = %d",
+ db->account_db, account_id )
+ ) {
+ Sql_ShowDebug(sql_handle);
+ return false;
+ }
+
+ if( SQL_SUCCESS != Sql_NextRow(sql_handle) )
+ {// no such entry
+ Sql_FreeResult(sql_handle);
+ return false;
+ }
+
+ Sql_GetData(sql_handle, 0, &data, NULL); acc->account_id = atoi(data);
+ Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(acc->userid, data, sizeof(acc->userid));
+ Sql_GetData(sql_handle, 2, &data, NULL); safestrncpy(acc->pass, data, sizeof(acc->pass));
+ Sql_GetData(sql_handle, 3, &data, NULL); acc->sex = data[0];
+ Sql_GetData(sql_handle, 4, &data, NULL); safestrncpy(acc->email, data, sizeof(acc->email));
+ Sql_GetData(sql_handle, 5, &data, NULL); acc->level = atoi(data);
+ Sql_GetData(sql_handle, 6, &data, NULL); acc->state = strtoul(data, NULL, 10);
+ Sql_GetData(sql_handle, 7, &data, NULL); acc->unban_time = atol(data);
+ Sql_GetData(sql_handle, 8, &data, NULL); acc->expiration_time = atol(data);
+ Sql_GetData(sql_handle, 9, &data, NULL); acc->logincount = strtoul(data, NULL, 10);
+ Sql_GetData(sql_handle, 10, &data, NULL); safestrncpy(acc->lastlogin, data, sizeof(acc->lastlogin));
+ Sql_GetData(sql_handle, 11, &data, NULL); safestrncpy(acc->last_ip, data, sizeof(acc->last_ip));
+
+ Sql_FreeResult(sql_handle);
+
+
+ // retrieve account regs for the specified user
+ if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `str`,`value` FROM `%s` WHERE `type`='1' AND `account_id`='%d'", db->accreg_db, acc->account_id) )
+ {
+ Sql_ShowDebug(sql_handle);
+ return false;
+ }
+
+ acc->account_reg2_num = (int)Sql_NumRows(sql_handle);
+
+ while( SQL_SUCCESS == Sql_NextRow(sql_handle) )
+ {
+ char* data;
+ Sql_GetData(sql_handle, 0, &data, NULL); safestrncpy(acc->account_reg2[i].str, data, sizeof(acc->account_reg2[i].str));
+ Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(acc->account_reg2[i].value, data, sizeof(acc->account_reg2[i].value));
+ ++i;
+ }
+ Sql_FreeResult(sql_handle);
+
+ if( i != acc->account_reg2_num )
+ return false;
+
+ return true;
+}
+
+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;
+ int i;
+
+ // try
+ do
+ {
+
+ if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION") )
+ {
+ Sql_ShowDebug(sql_handle);
+ break;
+ }
+
+ if( is_new )
+ {// insert into account table
+ if( SQL_SUCCESS != SqlStmt_Prepare(stmt,
+ "INSERT INTO `%s` (`account_id`, `userid`, `user_pass`, `sex`, `email`, `level`, `state`, `unban_time`, `expiration_time`, `logincount`, `lastlogin`, `last_ip`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ db->account_db)
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, (void*)&acc->account_id, sizeof(acc->account_id))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void*)acc->userid, strlen(acc->userid))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, (void*)acc->pass, strlen(acc->pass))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_ENUM, (void*)&acc->sex, sizeof(acc->sex))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 4, SQLDT_STRING, (void*)&acc->email, strlen(acc->email))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 5, SQLDT_INT, (void*)&acc->level, sizeof(acc->level))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 6, SQLDT_UINT, (void*)&acc->state, sizeof(acc->state))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 7, SQLDT_LONG, (void*)&acc->unban_time, sizeof(acc->unban_time))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 8, SQLDT_INT, (void*)&acc->expiration_time, sizeof(acc->expiration_time))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 9, SQLDT_UINT, (void*)&acc->logincount, sizeof(acc->logincount))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 10, SQLDT_STRING, (void*)&acc->lastlogin, strlen(acc->lastlogin))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 11, SQLDT_STRING, (void*)&acc->last_ip, strlen(acc->last_ip))
+ || SQL_SUCCESS != SqlStmt_Execute(stmt)
+ ) {
+ SqlStmt_ShowDebug(stmt);
+ break;
+ }
+ }
+ else
+ {// update account table
+ stmt = SqlStmt_Malloc(sql_handle);
+ if( SQL_SUCCESS != SqlStmt_Prepare(stmt, "UPDATE `%s` SET `userid`=?,`user_pass`=?,`sex`=?,`email`=?,`level`=?,`state`=?,`unban_time`=?,`expiration_time`=?,`logincount`=?,`lastlogin`=?,`last_ip`=? WHERE `account_id` = '%d'", db->account_db, acc->account_id)
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void*)acc->userid, strlen(acc->userid))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void*)acc->pass, strlen(acc->pass))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_ENUM, (void*)&acc->sex, sizeof(acc->sex))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_STRING, (void*)acc->email, strlen(acc->email))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 4, SQLDT_INT, (void*)&acc->level, sizeof(acc->level))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 5, SQLDT_UINT, (void*)&acc->state, sizeof(acc->state))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 6, SQLDT_LONG, (void*)&acc->unban_time, sizeof(acc->unban_time))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 7, SQLDT_LONG, (void*)&acc->expiration_time, sizeof(acc->expiration_time))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 8, SQLDT_UINT, (void*)&acc->logincount, sizeof(acc->logincount))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 9, SQLDT_STRING, (void*)&acc->lastlogin, strlen(acc->lastlogin))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 10, SQLDT_STRING, (void*)&acc->last_ip, strlen(acc->last_ip))
+ || SQL_SUCCESS != SqlStmt_Execute(stmt)
+ ) {
+ SqlStmt_ShowDebug(stmt);
+ break;
+ }
+ }
+
+ // remove old account regs
+ if( SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `type`='1' AND `account_id`='%d'", db->accreg_db, acc->account_id) )
+ {
+ Sql_ShowDebug(sql_handle);
+ break;
+ }
+ // insert new account regs
+ if( SQL_SUCCESS != SqlStmt_Prepare(stmt, "INSERT INTO `%s` (`type`, `account_id`, `str`, `value`) VALUES ( 1 , '%d' , ? , ? );", db->accreg_db, acc->account_id) )
+ {
+ SqlStmt_ShowDebug(stmt);
+ break;
+ }
+ for( i = 0; i < acc->account_reg2_num; ++i )
+ {
+ if( SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void*)acc->account_reg2[i].str, strlen(acc->account_reg2[i].str))
+ || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void*)acc->account_reg2[i].value, strlen(acc->account_reg2[i].value))
+ || SQL_SUCCESS != SqlStmt_Execute(stmt)
+ ) {
+ SqlStmt_ShowDebug(stmt);
+ break;
+ }
+ }
+ if( i < acc->account_reg2_num )
+ {
+ result = false;
+ break;
+ }
+
+ // if we got this far, everything was successful
+ result = true;
+
+ } while(0);
+ // finally
+
+ result &= ( SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") );
+ SqlStmt_Free(stmt);
+
+ return result;
+}
diff --git a/src/login/account_txt.c b/src/login/account_txt.c
new file mode 100644
index 000000000..cfcb6fb8f
--- /dev/null
+++ b/src/login/account_txt.c
@@ -0,0 +1,622 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/db.h"
+#include "../common/lock.h"
+#include "../common/malloc.h"
+#include "../common/mmo.h"
+#include "../common/showmsg.h"
+#include "../common/strlib.h"
+#include "../common/timer.h"
+#include "account.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/// global defines
+#define ACCOUNT_TXT_DB_VERSION 20080409
+#define AUTHS_BEFORE_SAVE 10 // flush every 10 saves
+#define AUTH_SAVING_INTERVAL 60000 // flush every 10 minutes
+
+/// internal structure
+typedef struct AccountDB_TXT
+{
+ AccountDB vtable; // public interface
+
+ DBMap* accounts; // in-memory accounts storage
+ int next_account_id; // auto_increment
+ int auths_before_save; // prevents writing to disk too often
+ int save_timer; // save timer id
+
+ char account_db[1024]; // account data storage file
+ bool case_sensitive; // how to look up usernames
+
+} AccountDB_TXT;
+
+/// internal structure
+typedef struct AccountDBIterator_TXT
+{
+ AccountDBIterator vtable; // public interface
+
+ DBIterator* iter;
+} AccountDBIterator_TXT;
+
+/// internal functions
+static bool account_db_txt_init(AccountDB* self);
+static void account_db_txt_destroy(AccountDB* self);
+static bool account_db_txt_get_property(AccountDB* self, const char* key, char* buf, size_t buflen);
+static bool account_db_txt_set_property(AccountDB* self, const char* option, const char* value);
+static bool account_db_txt_create(AccountDB* self, struct mmo_account* acc);
+static bool account_db_txt_remove(AccountDB* self, const int account_id);
+static bool account_db_txt_save(AccountDB* self, const struct mmo_account* acc);
+static bool account_db_txt_load_num(AccountDB* self, struct mmo_account* acc, const int account_id);
+static bool account_db_txt_load_str(AccountDB* self, struct mmo_account* acc, const char* userid);
+static AccountDBIterator* account_db_txt_iterator(AccountDB* self);
+static void account_db_txt_iter_destroy(AccountDBIterator* self);
+static bool account_db_txt_iter_next(AccountDBIterator* self, struct mmo_account* acc);
+
+static bool mmo_auth_fromstr(struct mmo_account* acc, char* str, unsigned int version);
+static bool mmo_auth_tostr(const struct mmo_account* acc, char* str);
+static void mmo_auth_sync(AccountDB_TXT* self);
+static int mmo_auth_sync_timer(int tid, unsigned int tick, int id, intptr data);
+
+/// public constructor
+AccountDB* account_db_txt(void)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)aCalloc(1, sizeof(AccountDB_TXT));
+
+ // set up the vtable
+ db->vtable.init = &account_db_txt_init;
+ db->vtable.destroy = &account_db_txt_destroy;
+ db->vtable.get_property = &account_db_txt_get_property;
+ db->vtable.set_property = &account_db_txt_set_property;
+ db->vtable.save = &account_db_txt_save;
+ db->vtable.create = &account_db_txt_create;
+ db->vtable.remove = &account_db_txt_remove;
+ db->vtable.load_num = &account_db_txt_load_num;
+ db->vtable.load_str = &account_db_txt_load_str;
+ db->vtable.iterator = &account_db_txt_iterator;
+
+ // initialize to default values
+ db->accounts = NULL;
+ db->next_account_id = START_ACCOUNT_NUM;
+ db->auths_before_save = AUTHS_BEFORE_SAVE;
+ db->save_timer = INVALID_TIMER;
+ safestrncpy(db->account_db, "save/account.txt", sizeof(db->account_db));
+ db->case_sensitive = false;
+
+ return &db->vtable;
+}
+
+
+/* ------------------------------------------------------------------------- */
+
+
+/// opens accounts file, loads it, and starts a periodic saving timer
+static bool account_db_txt_init(AccountDB* self)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)self;
+ DBMap* accounts;
+ FILE* fp;
+ char line[2048];
+ unsigned int version = 0;
+
+ // create accounts database
+ db->accounts = idb_alloc(DB_OPT_RELEASE_DATA);
+ accounts = db->accounts;
+
+ // open data file
+ fp = fopen(db->account_db, "r");
+ if( fp == NULL )
+ {
+ // no account file -> no account -> no login, including char-server (ERROR)
+ ShowError(CL_RED"account_db_txt_init: Accounts file [%s] not found."CL_RESET"\n", db->account_db);
+ return false;
+ }
+
+ // load data file
+ while( fgets(line, sizeof(line), fp) != NULL )
+ {
+ int account_id, n;
+ unsigned int v;
+ struct mmo_account acc;
+ struct mmo_account* tmp;
+ struct DBIterator* iter;
+ int (*compare)(const char* str1, const char* str2) = ( db->case_sensitive ) ? strcmp : stricmp;
+
+ if( line[0] == '/' && line[1] == '/' )
+ continue;
+
+ if( sscanf(line, "%d%n", &v, &n) == 1 && line[n] == '\n' )
+ {// format version definition
+ version = v;
+ continue;
+ }
+
+ if( sscanf(line, "%d\t%%newid%%%n", &account_id, &n) == 1 && line[n] == '\n' )
+ {// auto-increment
+ if( account_id > db->next_account_id )
+ db->next_account_id = account_id;
+ continue;
+ }
+
+ if( !mmo_auth_fromstr(&acc, line, version) )
+ {
+ ShowError("account_db_txt_init: skipping invalid data: %s", line);
+ continue;
+ }
+
+ // apply constraints & checks here
+ if( acc.sex != 'S' && (acc.account_id < START_ACCOUNT_NUM || acc.account_id > END_ACCOUNT_NUM) )
+ ShowWarning("account_db_txt_init: account %d:'%s' has ID outside of the defined range for accounts (min:%d max:%d)!\n", acc.account_id, acc.userid, START_ACCOUNT_NUM, END_ACCOUNT_NUM);
+
+ iter = accounts->iterator(accounts);
+ for( tmp = (struct mmo_account*)iter->first(iter,NULL); iter->exists(iter); tmp = (struct mmo_account*)iter->next(iter,NULL) )
+ if( compare(acc.userid, tmp->userid) == 0 )
+ break;
+ iter->destroy(iter);
+
+ if( tmp != NULL )
+ {// entry with identical username
+ ShowWarning("account_db_txt_init: account %d:'%s' has same username as account %d. The account will be inaccessible!\n", acc.account_id, acc.userid, tmp->account_id);
+ }
+
+ if( idb_get(accounts, acc.account_id) != NULL )
+ {// account id already occupied
+ ShowError("account_db_txt_init: ID collision for account id %d! Discarding data for account '%s'...\n", acc.account_id, acc.userid);
+ continue;
+ }
+
+ // record entry in db
+ tmp = (struct mmo_account*)aMalloc(sizeof(struct mmo_account));
+ memcpy(tmp, &acc, sizeof(struct mmo_account));
+ idb_put(accounts, acc.account_id, tmp);
+
+ if( db->next_account_id < acc.account_id)
+ db->next_account_id = acc.account_id + 1;
+ }
+
+ // close data file
+ fclose(fp);
+
+ // initialize data saving timer
+ add_timer_func_list(mmo_auth_sync_timer, "mmo_auth_sync_timer");
+ db->save_timer = add_timer_interval(gettick() + AUTH_SAVING_INTERVAL, mmo_auth_sync_timer, 0, (int)db, AUTH_SAVING_INTERVAL);
+
+ return true;
+}
+
+/// flush accounts db, close savefile and deallocate structures
+static void account_db_txt_destroy(AccountDB* self)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)self;
+ DBMap* accounts = db->accounts;
+
+ // stop saving timer
+ delete_timer(db->save_timer, mmo_auth_sync_timer);
+
+ // write data
+ mmo_auth_sync(db);
+
+ // delete accounts database
+ accounts->destroy(accounts, NULL);
+ db->accounts = NULL;
+
+ // delete entire structure
+ aFree(db);
+}
+
+/// Gets a property from this database.
+static bool account_db_txt_get_property(AccountDB* self, const char* key, char* buf, size_t buflen)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)self;
+ const char* signature = "account.txt.";
+
+ if( strcmp(key, "engine.name") == 0 )
+ {
+ safesnprintf(buf, buflen, "txt");
+ return true;
+ }
+ if( strcmp(key, "engine.version") == 0 )
+ {
+ safesnprintf(buf, buflen, "%d", ACCOUNT_TXT_DB_VERSION);
+ return true;
+ }
+ if( strcmp(key, "engine.comment") == 0 )
+ {
+ safesnprintf(buf, buflen, "TXT Account Database %d", ACCOUNT_TXT_DB_VERSION);
+ return true;
+ }
+
+ if( strncmp(key, signature, strlen(signature)) != 0 )
+ return false;
+
+ key += strlen(signature);
+
+ if( strcmpi(key, "account_db") == 0 )
+ safesnprintf(buf, buflen, "%s", db->account_db);
+ else if( strcmpi(key, "case_sensitive") == 0 )
+ safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0));
+ else
+ return false;// not found
+
+ return true;
+}
+
+/// Sets a property in this database.
+static bool account_db_txt_set_property(AccountDB* self, const char* key, const char* value)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)self;
+ const char* signature = "account.txt.";
+
+ if( strncmp(key, signature, strlen(signature)) != 0 )
+ return false;
+
+ key += strlen(signature);
+
+ if( strcmpi(key, "account_db") == 0 )
+ safestrncpy(db->account_db, value, sizeof(db->account_db));
+ else if( strcmpi(key, "case_sensitive") == 0 )
+ db->case_sensitive = config_switch(value);
+ else // no match
+ return false;
+
+ return true;
+}
+
+/// Add a new entry for this account to the account db and save it.
+/// 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_txt_create(AccountDB* self, struct mmo_account* acc)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)self;
+ DBMap* accounts = db->accounts;
+ struct mmo_account* tmp;
+
+ // decide on the account id to assign
+ int account_id = ( acc->account_id != -1 ) ? acc->account_id : db->next_account_id;
+
+ // absolute maximum
+ if( account_id > END_ACCOUNT_NUM )
+ return false;
+
+ // check if the account_id is free
+ tmp = idb_get(accounts, account_id);
+ if( tmp != NULL )
+ {// error condition - entry already present
+ ShowError("account_db_txt_create: cannot create account %d:'%s', this id is already occupied by %d:'%s'!\n", account_id, acc->userid, account_id, tmp->userid);
+ return false;
+ }
+
+ // copy the data and store it in the db
+ CREATE(tmp, struct mmo_account, 1);
+ memcpy(tmp, acc, sizeof(struct mmo_account));
+ tmp->account_id = account_id;
+ idb_put(accounts, account_id, tmp);
+
+ // increment the auto_increment value
+ if( account_id >= db->next_account_id )
+ db->next_account_id = account_id + 1;
+
+ // flush data
+ mmo_auth_sync(db);
+
+ // write output
+ acc->account_id = account_id;
+
+ return true;
+}
+
+/// find an existing entry for this account id and delete it
+static bool account_db_txt_remove(AccountDB* self, const int account_id)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)self;
+ DBMap* accounts = db->accounts;
+
+ //TODO: find out if this really works
+ struct mmo_account* tmp = idb_remove(accounts, account_id);
+ if( tmp == NULL )
+ {// error condition - entry not present
+ ShowError("account_db_txt_remove: no such account with id %d\n", account_id);
+ return false;
+ }
+
+ // flush data
+ mmo_auth_sync(db);
+
+ return true;
+}
+
+/// rewrite the data stored in the account_db with the one provided
+static bool account_db_txt_save(AccountDB* self, const struct mmo_account* acc)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)self;
+ DBMap* accounts = db->accounts;
+ int account_id = acc->account_id;
+
+ // retrieve previous data
+ struct mmo_acount* tmp = idb_get(accounts, account_id);
+ if( tmp == NULL )
+ {// error condition - entry not found
+ return false;
+ }
+
+ // overwrite with new data
+ memcpy(tmp, acc, sizeof(struct mmo_account));
+
+ // modify save counter and save if needed
+ if( --db->auths_before_save == 0 )
+ mmo_auth_sync(db);
+
+ return true;
+}
+
+/// retrieve data from db and store it in the provided data structure
+static bool account_db_txt_load_num(AccountDB* self, struct mmo_account* acc, const int account_id)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)self;
+ DBMap* accounts = db->accounts;
+
+ // retrieve data
+ struct mmo_account* tmp = idb_get(accounts, account_id);
+ if( tmp == NULL )
+ {// entry not found
+ return false;
+ }
+
+ // store it
+ memcpy(acc, tmp, sizeof(struct mmo_account));
+
+ return true;
+}
+
+/// retrieve data from db and store it in the provided data structure
+static bool account_db_txt_load_str(AccountDB* self, struct mmo_account* acc, const char* userid)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)self;
+ DBMap* accounts = db->accounts;
+
+ // retrieve data
+ struct DBIterator* iter = accounts->iterator(accounts);
+ struct mmo_account* tmp;
+ int (*compare)(const char* str1, const char* str2) = ( db->case_sensitive ) ? strcmp : stricmp;
+
+ for( tmp = (struct mmo_account*)iter->first(iter,NULL); iter->exists(iter); tmp = (struct mmo_account*)iter->next(iter,NULL) )
+ if( compare(userid, tmp->userid) == 0 )
+ break;
+ iter->destroy(iter);
+
+ if( tmp == NULL )
+ {// entry not found
+ return false;
+ }
+
+ // store it
+ memcpy(acc, tmp, sizeof(struct mmo_account));
+
+ return true;
+}
+
+
+/// Returns a new forward iterator.
+static AccountDBIterator* account_db_txt_iterator(AccountDB* self)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)self;
+ DBMap* accounts = db->accounts;
+ AccountDBIterator_TXT* iter = (AccountDBIterator_TXT*)aCalloc(1, sizeof(AccountDBIterator_TXT));
+
+ // set up the vtable
+ iter->vtable.destroy = &account_db_txt_iter_destroy;
+ iter->vtable.next = &account_db_txt_iter_next;
+
+ // fill data
+ iter->iter = db_iterator(accounts);
+
+ return &iter->vtable;
+}
+
+
+/// Destroys this iterator, releasing all allocated memory (including itself).
+static void account_db_txt_iter_destroy(AccountDBIterator* self)
+{
+ AccountDBIterator_TXT* iter = (AccountDBIterator_TXT*)self;
+ dbi_destroy(iter->iter);
+ aFree(iter);
+}
+
+
+/// Fetches the next account in the database.
+static bool account_db_txt_iter_next(AccountDBIterator* self, struct mmo_account* acc)
+{
+ AccountDBIterator_TXT* iter = (AccountDBIterator_TXT*)self;
+ struct mmo_account* tmp = (struct mmo_account*)dbi_next(iter->iter);
+ if( dbi_exists(iter->iter) )
+ {
+ memcpy(acc, tmp, sizeof(struct mmo_account));
+ return true;
+ }
+ return false;
+}
+
+
+/// parse input string into the provided account data structure
+static bool mmo_auth_fromstr(struct mmo_account* a, char* str, unsigned int version)
+{
+ char* fields[32];
+ int count;
+ char* regs;
+ int i, n;
+
+ // zero out the destination first
+ memset(a, 0x00, sizeof(struct mmo_account));
+
+ // extract tab-separated columns from line
+ count = sv_split(str, strlen(str), 0, '\t', fields, ARRAYLENGTH(fields), SV_NOESCAPE_NOTERMINATE);
+
+ if( version == ACCOUNT_TXT_DB_VERSION && count == 13 )
+ {
+ a->account_id = strtol(fields[1], NULL, 10);
+ safestrncpy(a->userid, fields[2], sizeof(a->userid));
+ safestrncpy(a->pass, fields[3], sizeof(a->pass));
+ a->sex = fields[4][0];
+ safestrncpy(a->email, fields[5], sizeof(a->email));
+ a->level = strtoul(fields[6], NULL, 10);
+ a->state = strtoul(fields[7], NULL, 10);
+ a->unban_time = strtol(fields[8], NULL, 10);
+ a->expiration_time = strtol(fields[9], NULL, 10);
+ a->logincount = strtoul(fields[10], NULL, 10);
+ safestrncpy(a->lastlogin, fields[11], sizeof(a->lastlogin));
+ safestrncpy(a->last_ip, fields[12], sizeof(a->last_ip));
+ regs = fields[13];
+ }
+ else
+ if( version == 0 && count == 14 )
+ {
+ a->account_id = strtol(fields[1], NULL, 10);
+ safestrncpy(a->userid, fields[2], sizeof(a->userid));
+ safestrncpy(a->pass, fields[3], sizeof(a->pass));
+ safestrncpy(a->lastlogin, fields[4], sizeof(a->lastlogin));
+ a->sex = fields[5][0];
+ a->logincount = strtoul(fields[6], NULL, 10);
+ a->state = strtoul(fields[7], NULL, 10);
+ safestrncpy(a->email, fields[8], sizeof(a->email));
+ //safestrncpy(a->error_message, fields[9], sizeof(a->error_message));
+ a->expiration_time = strtol(fields[10], NULL, 10);
+ safestrncpy(a->last_ip, fields[11], sizeof(a->last_ip));
+ //safestrncpy(a->memo, fields[12], sizeof(a->memo));
+ a->unban_time = strtol(fields[13], NULL, 10);
+ regs = fields[14];
+ }
+ else
+ if( version == 0 && count == 13 )
+ {
+ a->account_id = strtol(fields[1], NULL, 10);
+ safestrncpy(a->userid, fields[2], sizeof(a->userid));
+ safestrncpy(a->pass, fields[3], sizeof(a->pass));
+ safestrncpy(a->lastlogin, fields[4], sizeof(a->lastlogin));
+ a->sex = fields[5][0];
+ a->logincount = strtoul(fields[6], NULL, 10);
+ a->state = strtoul(fields[7], NULL, 10);
+ safestrncpy(a->email, fields[8], sizeof(a->email));
+ //safestrncpy(a->error_message, fields[9], sizeof(a->error_message));
+ a->expiration_time = strtol(fields[10], NULL, 10);
+ safestrncpy(a->last_ip, fields[11], sizeof(a->last_ip));
+ //safestrncpy(a->memo, fields[12], sizeof(a->memo));
+ regs = fields[13];
+ }
+ else
+ if( version == 0 && count == 8 )
+ {
+ a->account_id = strtol(fields[1], NULL, 10);
+ safestrncpy(a->userid, fields[2], sizeof(a->userid));
+ safestrncpy(a->pass, fields[3], sizeof(a->pass));
+ safestrncpy(a->lastlogin, fields[4], sizeof(a->lastlogin));
+ a->sex = fields[5][0];
+ a->logincount = strtoul(fields[6], NULL, 10);
+ a->state = strtoul(fields[7], NULL, 10);
+ regs = fields[8];
+ }
+ else
+ {// unmatched row
+ return false;
+ }
+
+ // extract account regs
+ // {reg name<COMMA>reg value<SPACE>}*
+ n = 0;
+ for( i = 0; i < ACCOUNT_REG2_NUM; ++i )
+ {
+ char key[32];
+ char value[256];
+
+ regs += n;
+
+ if (sscanf(regs, "%31[^\t,],%255[^\t ] %n", key, value, &n) != 2)
+ {
+ // We must check if a str is void. If it's, we can continue to read other REG2.
+ // Account line will have something like: str2,9 ,9 str3,1 (here, ,9 is not good)
+ if (regs[0] == ',' && sscanf(regs, ",%[^\t ] %n", value, &n) == 1) {
+ i--;
+ continue;
+ } else
+ break;
+ }
+
+ safestrncpy(a->account_reg2[i].str, key, 32);
+ safestrncpy(a->account_reg2[i].value, value, 256);
+ }
+ a->account_reg2_num = i;
+
+ return true;
+}
+
+/// dump the contents of the account data structure into the provided string buffer
+static bool mmo_auth_tostr(const struct mmo_account* a, char* str)
+{
+ int i;
+ char* str_p = str;
+
+ str_p += sprintf(str_p, "%d\t%s\t%s\t%c\t%s\t%u\t%u\t%ld\t%ld\t%u\t%s\t%s\t",
+ a->account_id, a->userid, a->pass, a->sex, a->email, a->level,
+ a->state, (long)a->unban_time, (long)a->expiration_time,
+ a->logincount, a->lastlogin, a->last_ip);
+
+ for( i = 0; i < a->account_reg2_num; ++i )
+ if( a->account_reg2[i].str[0] )
+ str_p += sprintf(str_p, "%s,%s ", a->account_reg2[i].str, a->account_reg2[i].value);
+
+ return true;
+}
+
+/// dump the entire account db to disk
+static void mmo_auth_sync(AccountDB_TXT* db)
+{
+ int lock;
+ FILE *fp;
+ struct DBIterator* iter;
+ struct mmo_account* acc;
+
+ fp = lock_fopen(db->account_db, &lock);
+ if( fp == NULL )
+ {
+ return;
+ }
+
+ fprintf(fp, "%d\n", ACCOUNT_TXT_DB_VERSION); // savefile version
+
+ fprintf(fp, "// Accounts file: here are saved all information about the accounts.\n");
+ fprintf(fp, "// Structure: account ID, username, password, sex, email, level, state, unban time, expiration time, # of logins, last login time, last (accepted) login ip, repeated(register key, register value)\n");
+ fprintf(fp, "// where:\n");
+ fprintf(fp, "// sex : M or F for normal accounts, S for server accounts\n");
+ fprintf(fp, "// level : this account's gm level\n");
+ fprintf(fp, "// state : 0: account is ok, 1 to 256: error code of packet 0x006a + 1\n");
+ fprintf(fp, "// unban time : 0: no ban, <other value>: banned until the date (unix timestamp)\n");
+ fprintf(fp, "// expiration time : 0: unlimited account, <other value>: account expires on the date (unix timestamp)\n");
+
+ //TODO: sort?
+
+ iter = db->accounts->iterator(db->accounts);
+ for( acc = (struct mmo_account*)iter->first(iter,NULL); iter->exists(iter); acc = (struct mmo_account*)iter->next(iter,NULL) )
+ {
+ char buf[2048]; // ought to be big enough ^^
+ mmo_auth_tostr(acc, buf);
+ fprintf(fp, "%s\n", buf);
+ }
+ fprintf(fp, "%d\t%%newid%%\n", db->next_account_id);
+ iter->destroy(iter);
+
+ lock_fclose(fp, db->account_db, &lock);
+
+ // reset save counter
+ db->auths_before_save = AUTHS_BEFORE_SAVE;
+}
+
+static int mmo_auth_sync_timer(int tid, unsigned int tick, int id, intptr data)
+{
+ AccountDB_TXT* db = (AccountDB_TXT*)data;
+
+ if( db->auths_before_save < AUTHS_BEFORE_SAVE )
+ mmo_auth_sync(db); // db was modified, flush it
+
+ return 0;
+}
diff --git a/src/login/admin.c b/src/login/admin.c
index 9bce1a02f..c4116f16d 100644
--- a/src/login/admin.c
+++ b/src/login/admin.c
@@ -1,3 +1,6 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
#include "../common/cbasetypes.h"
#include "../common/mmo.h"
#include "../common/core.h"
@@ -10,6 +13,7 @@
#include "../common/version.h"
#include "../common/md5calc.h"
#include "../common/lock.h"
+#include "account.h"
#include "login.h"
#include <stdio.h>
@@ -17,31 +21,72 @@
#include <string.h>
#include <sys/stat.h> // for stat/lstat/fstat
-extern struct Login_Config login_config;
-
#define MAX_SERVERS 30
extern struct mmo_char_server server[MAX_SERVERS];
-extern struct mmo_account* auth_dat;
-extern uint32 auth_num;
-extern int account_id_count;
-extern char GM_account_filename[1024];
+extern AccountDB* accounts;
int charif_sendallwos(int sfd, unsigned char *buf, unsigned int len);
-int search_account_index(char* account_name);
-int mmo_auth_new(struct mmo_account* account);
-void mmo_auth_sync(void);
-int mmo_auth_tostr(char* str, struct mmo_account* p);
-int read_gm_account(void);
-void send_GM_accounts(int fd);
-int isGM(int account_id);
+bool check_password(const char* md5key, int passwdenc, const char* passwd, const char* refpass);
+int mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip);
+int parse_admin(int fd);
+
+
+bool ladmin_auth(struct login_session_data* sd, const char* ip)
+{
+ bool result = false;
+
+ if( str2ip(ip) != host2ip(login_config.admin_allowed_host) )
+ ShowNotice("'ladmin'-login: Connection in administration mode REFUSED - IP isn't authorised (ip: %s).\n", ip);
+ else
+ if( !login_config.admin_state )
+ ShowNotice("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (ip: %s)\n", ip);
+ else
+ if( !check_password(sd->md5key, sd->passwdenc, sd->passwd, login_config.admin_pass) )
+ ShowNotice("'ladmin'-login: Connection in administration mode REFUSED - invalid password (ip: %s)\n", ip);
+ else
+ {
+ ShowNotice("'ladmin'-login: Connection in administration mode accepted (ip: %s)\n", ip);
+ session[sd->fd]->func_parse = parse_admin;
+ result = true;
+ }
+
+ return result;
+}
//---------------------------------------
// Packet parsing for administation login
+//
+// List of supported operations:
+// 0x7530 - request server version (response: 0x7531)
+// 0x7938 - request server list (response: 0x7939)
+// 0x7920 - request entire list of accounts (response: 0x7921)
+// 0x794e - request message broadcast (response: 0x794f + 0x2726)
+
+// 0x7930 - request account creation (response: 0x7931)
+// 0x7932 - request account deletion (response: 0x7933 + 0x2730)
+
+// 0x7934 - request account password modification (response: 0x7935)
+// 0x7936 - request account state modification (response: 0x7937 + 0x2731)
+// 0x793a - request password check (response: 0x793b)
+// 0x793c - request account sex modification (response: 0x793d + 0x2723)
+// 0x793e - request account gm-level modification (response: 0x793f)
+// 0x7940 - request account email modification (response: 0x7941)
+// 0x7942 - request account memo modification (response: 0x7943)
+// 0x7948 - request account expiration-time modification - absolute (response: 0x7949)
+// 0x7950 - request account expiration-time modification - relative (response: 0x7951)
+// 0x794a - request account unban-time modification - absolute (response: 0x794b + 0x2731)
+// 0x794c - request account unban-time modification - relative (response: 0x794d + 0x2731)
+
+// 0x7944 - request account id lookup by name (response: 0x7945)
+// 0x7946 - request account name lookup by id (response: 0x7947)
+// 0x7952 - request account information lookup by name (response: 0x7953)
+// 0x7954 - request account information lookup by id (response: 0x7953)
//---------------------------------------
int parse_admin(int fd)
{
unsigned int i, j;
char* account_name;
+ struct mmo_account acc;
uint32 ipl = session[fd]->client_addr;
char ip[16];
@@ -75,7 +120,7 @@ int parse_admin(int fd)
WFIFOSET(fd,10);
RFIFOSKIP(fd,2);
break;
-
+/*
case 0x7920: // Request of an accounts list
if (RFIFOREST(fd) < 10)
return 0;
@@ -130,158 +175,145 @@ int parse_admin(int fd)
DELETE_BUFFER(id);
}
break;
-
+*/
case 0x7930: // Request for an account creation
if (RFIFOREST(fd) < 91)
return 0;
- {
- struct mmo_account ma;
- safestrncpy(ma.userid, (char*)RFIFOP(fd, 2), NAME_LENGTH);
- safestrncpy(ma.pass, (char*)RFIFOP(fd,26), NAME_LENGTH);
- safestrncpy(ma.email, (char*)RFIFOP(fd,51), 40);
- memcpy(ma.lastlogin, "-", 2);
- ma.sex = RFIFOB(fd,50);
- RFIFOSKIP(fd,91);
-
- WFIFOW(fd,0) = 0x7931;
- WFIFOL(fd,2) = 0xffffffff; // invalid account id
- safestrncpy((char*)WFIFOP(fd,6), ma.userid, 24);
- if (strlen(ma.userid) < 4 || strlen(ma.pass) < 4) {
- ShowNotice("'ladmin': Attempt to create an invalid account (account or pass is too short, ip: %s)\n", ip);
- } else if (ma.sex != 'F' && ma.sex != 'M') {
- ShowNotice("'ladmin': Attempt to create an invalid account (account: %s, received pass: %s, invalid sex, ip: %s)\n", ma.userid, ma.pass, ip);
- } else if (account_id_count > END_ACCOUNT_NUM) {
- ShowNotice("'ladmin': Attempt to create an account, but there is no more available id number (account: %s, pass: %s, sex: %c, ip: %s)\n", ma.userid, ma.pass, ma.sex, ip);
- } else {
- remove_control_chars(ma.userid);
- remove_control_chars(ma.pass);
- remove_control_chars(ma.email);
- ARR_FIND( 0, auth_num, i, strncmp(auth_dat[i].userid, ma.userid, 24) == 0 );
- if( i < auth_num )
- ShowNotice("'ladmin': Attempt to create an already existing account (account: %s, pass: %s, received pass: %s, ip: %s)\n", auth_dat[i].userid, auth_dat[i].pass, ma.pass, ip);
- else
- {
- int new_id;
- new_id = mmo_auth_new(&ma);
- ShowNotice("'ladmin': Account creation (account: %s (id: %d), pass: %s, sex: %c, email: %s, ip: %s)\n", ma.userid, new_id, ma.pass, ma.sex, auth_dat[i].email, ip);
- WFIFOL(fd,2) = new_id;
- mmo_auth_sync();
- }
- }
- WFIFOSET(fd,30);
- }
+ {
+ struct mmo_account ma;
+ safestrncpy(ma.userid, (char*)RFIFOP(fd, 2), sizeof(ma.userid));
+ safestrncpy(ma.pass, (char*)RFIFOP(fd,26), sizeof(ma.pass));
+ ma.sex = RFIFOB(fd,50);
+ safestrncpy(ma.email, (char*)RFIFOP(fd,51), sizeof(ma.email));
+ safestrncpy(ma.lastlogin, "-", sizeof(ma.lastlogin));
+
+ ShowNotice("'ladmin': Account creation request (account: %s pass: %s, sex: %c, email: %s, ip: %s)\n", ma.userid, ma.pass, ma.sex, ma.email, ip);
+
+ WFIFOW(fd,0) = 0x7931;
+ WFIFOL(fd,2) = mmo_auth_new(ma.userid, ma.pass, ma.sex, ip);
+ safestrncpy((char*)WFIFOP(fd,6), ma.userid, 24);
+ WFIFOSET(fd,30);
+ }
+ RFIFOSKIP(fd,91);
break;
-
+/*
case 0x7932: // Request for an account deletion
if (RFIFOREST(fd) < 26)
return 0;
- WFIFOW(fd,0) = 0x7933;
- WFIFOL(fd,2) = 0xFFFFFFFF;
- account_name = (char*)RFIFOP(fd,2);
+ {
+ struct mmo_account acc;
+
+ char* account_name = (char*)RFIFOP(fd,2);
account_name[23] = '\0';
- remove_control_chars(account_name);
- i = search_account_index(account_name);
- if (i != -1) {
+
+ WFIFOW(fd,0) = 0x7933;
+
+ if( accounts->load_str(accounts, &acc, account_name) )
+ {
// Char-server is notified of deletion (for characters deletion).
unsigned char buf[65535];
WBUFW(buf,0) = 0x2730;
- WBUFL(buf,2) = auth_dat[i].account_id;
+ WBUFL(buf,2) = acc.account_id;
charif_sendallwos(-1, buf, 6);
+
// send answer
- memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24);
- WFIFOL(fd,2) = auth_dat[i].account_id;
- // save deleted account in log file
- ShowNotice("'ladmin': Account deletion (account: %s, id: %d, ip: %s) - saved in next line:\n", auth_dat[i].userid, auth_dat[i].account_id, ip);
- mmo_auth_tostr((char*)buf, &auth_dat[i]);
- ShowNotice("%s\n", buf);
+ memcpy(WFIFOP(fd,6), acc.userid, 24);
+ WFIFOL(fd,2) = acc.account_id;
+
// delete account
- memset(auth_dat[i].userid, '\0', sizeof(auth_dat[i].userid));
+ memset(acc.userid, '\0', sizeof(acc.userid));
auth_dat[i].account_id = -1;
mmo_auth_sync();
} else {
+ WFIFOL(fd,2) = -1;
memcpy(WFIFOP(fd,6), account_name, 24);
ShowNotice("'ladmin': Attempt to delete an unknown account (account: %s, ip: %s)\n", account_name, ip);
}
WFIFOSET(fd,30);
+ }
RFIFOSKIP(fd,26);
break;
-
+*/
case 0x7934: // Request to change a password
if (RFIFOREST(fd) < 50)
return 0;
- WFIFOW(fd,0) = 0x7935;
- WFIFOL(fd,2) = 0xFFFFFFFF; /// WTF??? an unsigned being set to a -1
- account_name = (char*)RFIFOP(fd,2);
+ {
+ struct mmo_account acc;
+
+ char* account_name = (char*)RFIFOP(fd,2);
account_name[23] = '\0';
- remove_control_chars(account_name);
- i = search_account_index(account_name);
- if (i != -1) {
- memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24);
- memcpy(auth_dat[i].pass, RFIFOP(fd,26), 24);
- auth_dat[i].pass[23] = '\0';
- remove_control_chars(auth_dat[i].pass);
- WFIFOL(fd,2) = auth_dat[i].account_id;
- ShowNotice("'ladmin': Modification of a password (account: %s, new password: %s, ip: %s)\n", auth_dat[i].userid, auth_dat[i].pass, ip);
- mmo_auth_sync();
- } else {
- memcpy(WFIFOP(fd,6), account_name, 24);
+
+ WFIFOW(fd,0) = 0x7935;
+
+ if( accounts->load_str(accounts, &acc, account_name) )
+ {
+ WFIFOL(fd,2) = acc.account_id;
+ safestrncpy((char*)WFIFOP(fd,6), acc.userid, 24);
+ safestrncpy(acc.pass, (char*)RFIFOP(fd,26), 24);
+ ShowNotice("'ladmin': Modification of a password (account: %s, new password: %s, ip: %s)\n", acc.userid, acc.pass, ip);
+
+ accounts->save(accounts, &acc);
+ }
+ else
+ {
+ WFIFOL(fd,2) = -1;
+ safestrncpy((char*)WFIFOP(fd,6), account_name, 24);
ShowNotice("'ladmin': Attempt to modify the password of an unknown account (account: %s, ip: %s)\n", account_name, ip);
}
+
WFIFOSET(fd,30);
+ }
RFIFOSKIP(fd,50);
break;
case 0x7936: // Request to modify a state
if (RFIFOREST(fd) < 50)
return 0;
+ {
+ struct mmo_account acc;
+
+ char* account_name = (char*)RFIFOP(fd,2);
+ uint32 state = RFIFOL(fd,26);
+ account_name[23] = '\0';
+
+ WFIFOW(fd,0) = 0x7937;
+
+ if( accounts->load_str(accounts, &acc, account_name) )
{
- char error_message[20];
- uint32 statut;
- WFIFOW(fd,0) = 0x7937;
- WFIFOL(fd,2) = 0xFFFFFFFF; // WTF???
- account_name = (char*)RFIFOP(fd,2);
- account_name[23] = '\0';
- remove_control_chars(account_name);
- statut = RFIFOL(fd,26);
- memcpy(error_message, RFIFOP(fd,30), 20);
- error_message[19] = '\0';
- remove_control_chars(error_message);
- if (statut != 7 || error_message[0] == '\0') { // 7: // 6 = Your are Prohibited to log in until %s
- strcpy(error_message, "-");
- }
- i = search_account_index(account_name);
- if (i != -1) {
- memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24);
- WFIFOL(fd,2) = auth_dat[i].account_id;
- if (auth_dat[i].state == statut && strcmp(auth_dat[i].error_message, error_message) == 0)
- ShowNotice("'ladmin': Modification of a state, but the state of the account is already the good state (account: %s, received state: %d, ip: %s)\n", account_name, statut, ip);
- else {
- if (statut == 7)
- ShowNotice("'ladmin': Modification of a state (account: %s, new state: %d - prohibited to login until '%s', ip: %s)\n", auth_dat[i].userid, statut, error_message, ip);
- else
- ShowNotice("'ladmin': Modification of a state (account: %s, new state: %d, ip: %s)\n", auth_dat[i].userid, statut, ip);
- if (auth_dat[i].state == 0) {
- unsigned char buf[16];
- WBUFW(buf,0) = 0x2731;
- WBUFL(buf,2) = auth_dat[i].account_id;
- WBUFB(buf,6) = 0; // 0: change of statut, 1: ban
- WBUFL(buf,7) = statut; // status or final date of a banishment
- charif_sendallwos(-1, buf, 11);
- }
- auth_dat[i].state = statut;
- memcpy(auth_dat[i].error_message, error_message, 20);
- mmo_auth_sync();
+ memcpy(WFIFOP(fd,6), acc.userid, 24);
+ WFIFOL(fd,2) = acc.account_id;
+
+ if (acc.state == state)
+ ShowNotice("'ladmin': Modification of a state, but the state of the account already has this value (account: %s, received state: %d, ip: %s)\n", account_name, state, ip);
+ else
+ {
+ ShowNotice("'ladmin': Modification of a state (account: %s, new state: %d, ip: %s)\n", acc.userid, state, ip);
+
+ if (acc.state == 0) {
+ unsigned char buf[16];
+ WBUFW(buf,0) = 0x2731;
+ WBUFL(buf,2) = acc.account_id;
+ WBUFB(buf,6) = 0; // 0: change of statut, 1: ban
+ WBUFL(buf,7) = state; // status or final date of a banishment
+ charif_sendallwos(-1, buf, 11);
}
- } else {
- memcpy(WFIFOP(fd,6), account_name, 24);
- ShowNotice("'ladmin': Attempt to modify the state of an unknown account (account: %s, received state: %d, ip: %s)\n", account_name, statut, ip);
+ acc.state = state;
+ accounts->save(accounts, &acc);
}
- WFIFOL(fd,30) = statut;
}
+ else
+ {
+ ShowNotice("'ladmin': Attempt to modify the state of an unknown account (account: %s, received state: %d, ip: %s)\n", account_name, state, ip);
+ WFIFOL(fd,2) = -1;
+ memcpy(WFIFOP(fd,6), account_name, 24);
+ }
+
+ WFIFOL(fd,30) = state;
WFIFOSET(fd,34);
+ }
RFIFOSKIP(fd,50);
break;
-
+/*
case 0x7938: // Request for servers list and # of online players
{
uint8 server_num = 0;
@@ -312,18 +344,18 @@ int parse_admin(int fd)
account_name = (char*)RFIFOP(fd,2);
account_name[23] = '\0';
remove_control_chars(account_name);
- i = search_account_index(account_name);
- if (i != -1) {
+ if( accounts->load_str(accounts, &acc, account_name) )
+ {
char pass[25];
memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24);
memcpy(pass, RFIFOP(fd,26), 24);
pass[24] = '\0';
remove_control_chars(pass);
- if (strcmp(auth_dat[i].pass, pass) == 0) {
- WFIFOL(fd,2) = auth_dat[i].account_id;
- ShowNotice("'ladmin': Check of password OK (account: %s, password: %s, ip: %s)\n", auth_dat[i].userid, auth_dat[i].pass, ip);
+ if (strcmp(acc.pass, pass) == 0) {
+ WFIFOL(fd,2) = acc.account_id;
+ ShowNotice("'ladmin': Check of password OK (account: %s, password: %s, ip: %s)\n", acc.userid, acc.pass, ip);
} else {
- ShowNotice("'ladmin': Failure of password check (account: %s, proposed pass: %s, ip: %s)\n", auth_dat[i].userid, pass, ip);
+ ShowNotice("'ladmin': Failure of password check (account: %s, proposed pass: %s, ip: %s)\n", acc.userid, pass, ip);
}
} else {
memcpy(WFIFOP(fd,6), account_name, 24);
@@ -337,7 +369,7 @@ int parse_admin(int fd)
if (RFIFOREST(fd) < 27)
return 0;
WFIFOW(fd,0) = 0x793d;
- WFIFOL(fd,2) = 0xFFFFFFFF; // WTF???
+ WFIFOL(fd,2) = 0xFFFFFFFF; // -1
account_name = (char*)RFIFOP(fd,2);
account_name[23] = '\0';
remove_control_chars(account_name);
@@ -351,22 +383,25 @@ int parse_admin(int fd)
else
ShowNotice("'ladmin': Attempt to give an invalid sex (account: %s, received sex: 'control char', ip: %s)\n", account_name, ip);
} else {
- i = search_account_index(account_name);
- if (i != -1) {
- memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24);
- if (auth_dat[i].sex != ((sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'))) {
+ if( accounts->load_str(accounts, &acc, account_name) )
+ {
+ memcpy(WFIFOP(fd,6), acc.userid, 24);
+ if (acc.sex != sex)
+ {
unsigned char buf[16];
- WFIFOL(fd,2) = auth_dat[i].account_id;
- auth_dat[i].sex = (sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm');
- ShowNotice("'ladmin': Modification of a sex (account: %s, new sex: %c, ip: %s)\n", auth_dat[i].userid, sex, ip);
- mmo_auth_sync();
+ ShowNotice("'ladmin': Modification of a sex (account: %s, new sex: %c, ip: %s)\n", acc.userid, sex, ip);
+
+ WFIFOL(fd,2) = acc.account_id;
+ acc.sex = sex;
+ accounts->save(accounts, &acc);
+
// send to all char-server the change
WBUFW(buf,0) = 0x2723;
- WBUFL(buf,2) = auth_dat[i].account_id;
- WBUFB(buf,6) = auth_dat[i].sex;
+ WBUFL(buf,2) = acc.account_id;
+ WBUFB(buf,6) = acc.sex;
charif_sendallwos(-1, buf, 7);
} else {
- ShowNotice("'ladmin': Modification of a sex, but the sex is already the good sex (account: %s, sex: %c, ip: %s)\n", auth_dat[i].userid, sex, ip);
+ ShowNotice("'ladmin': Modification of a sex, but the sex is already the good sex (account: %s, sex: %c, ip: %s)\n", acc.userid, sex, ip);
}
} else {
ShowNotice("'ladmin': Attempt to modify the sex of an unknown account (account: %s, received sex: %c, ip: %s)\n", account_name, sex, ip);
@@ -381,82 +416,31 @@ int parse_admin(int fd)
if (RFIFOREST(fd) < 27)
return 0;
WFIFOW(fd,0) = 0x793f;
- WFIFOL(fd,2) = 0xFFFFFFFF; // WTF???
+ WFIFOL(fd,2) = 0xFFFFFFFF; // -1
account_name = (char*)RFIFOP(fd,2);
account_name[23] = '\0';
remove_control_chars(account_name);
memcpy(WFIFOP(fd,6), account_name, 24);
+ {
+ char new_gm_level;
+ new_gm_level = RFIFOB(fd,26);
+ if( new_gm_level < 0 || new_gm_level > 99 )
+ ShowNotice("'ladmin': Attempt to give an invalid GM level (account: %s, received GM level: %d, ip: %s)\n", account_name, (int)new_gm_level, ip);
+ else
+ if( !accounts->load_str(accounts, &acc, account_name) )
+ ShowNotice("'ladmin': Attempt to modify the GM level of an unknown account (account: %s, received GM level: %d, ip: %s)\n", account_name, (int)new_gm_level, ip);
+ else
{
- char new_gm_level;
- new_gm_level = RFIFOB(fd,26);
- if (new_gm_level < 0 || new_gm_level > 99) {
- ShowNotice("'ladmin': Attempt to give an invalid GM level (account: %s, received GM level: %d, ip: %s)\n", account_name, (int)new_gm_level, ip);
- } else {
- i = search_account_index(account_name);
- if (i != -1) {
- int acc = auth_dat[i].account_id;
- memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24);
- if (isGM(acc) != new_gm_level) {
- // modification of the file
- FILE *fp, *fp2;
- int lock;
- char line[512];
- int GM_account, GM_level;
- int modify_flag;
- char tmpstr[24];
- time_t raw_time;
- if ((fp2 = lock_fopen(GM_account_filename, &lock)) != NULL) {
- if ((fp = fopen(GM_account_filename, "r")) != NULL) {
- time(&raw_time);
- strftime(tmpstr, 23, login_config.date_format, localtime(&raw_time));
- modify_flag = 0;
- // read/write GM file
- while(fgets(line, sizeof(line), fp))
- {
- while(line[0] != '\0' && (line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r'))
- line[strlen(line)-1] = '\0'; // TODO: remove this
- if ((line[0] == '/' && line[1] == '/') || line[0] == '\0')
- fprintf(fp2, "%s\n", line);
- else {
- if (sscanf(line, "%d %d", &GM_account, &GM_level) != 2 && sscanf(line, "%d: %d", &GM_account, &GM_level) != 2)
- fprintf(fp2, "%s\n", line);
- else if (GM_account != acc)
- fprintf(fp2, "%s\n", line);
- else if (new_gm_level < 1) {
- fprintf(fp2, "// %s: 'ladmin' GM level removed on account %d '%s' (previous level: %d)\n//%d %d\n", tmpstr, acc, auth_dat[i].userid, GM_level, acc, new_gm_level);
- modify_flag = 1;
- } else {
- fprintf(fp2, "// %s: 'ladmin' GM level on account %d '%s' (previous level: %d)\n%d %d\n", tmpstr, acc, auth_dat[i].userid, GM_level, acc, new_gm_level);
- modify_flag = 1;
- }
- }
- }
- if (modify_flag == 0)
- fprintf(fp2, "// %s: 'ladmin' GM level on account %d '%s' (previous level: 0)\n%d %d\n", tmpstr, acc, auth_dat[i].userid, acc, new_gm_level);
- fclose(fp);
- } else {
- ShowNotice("'ladmin': Attempt to modify of a GM level - impossible to read GM accounts file (account: %s (%d), received GM level: %d, ip: %s)\n", auth_dat[i].userid, acc, (int)new_gm_level, ip);
- }
- if (lock_fclose(fp2, GM_account_filename, &lock) == 0) {
- WFIFOL(fd,2) = acc;
- ShowNotice("'ladmin': Modification of a GM level (account: %s (%d), new GM level: %d, ip: %s)\n", auth_dat[i].userid, acc, (int)new_gm_level, ip);
- // read and send new GM informations
- read_gm_account();
- send_GM_accounts(-1);
- } else {
- ShowNotice("'ladmin': Attempt to modify of a GM level - impossible to write GM accounts file (account: %s (%d), received GM level: %d, ip: %s)\n", auth_dat[i].userid, acc, (int)new_gm_level, ip);
- }
- } else {
- ShowNotice("'ladmin': Attempt to modify of a GM level - impossible to write GM accounts file (account: %s (%d), received GM level: %d, ip: %s)\n", auth_dat[i].userid, acc, (int)new_gm_level, ip);
- }
- } else {
- ShowNotice("'ladmin': Attempt to modify of a GM level, but the GM level is already the good GM level (account: %s (%d), GM level: %d, ip: %s)\n", auth_dat[i].userid, acc, (int)new_gm_level, ip);
- }
- } else {
- ShowNotice("'ladmin': Attempt to modify the GM level of an unknown account (account: %s, received GM level: %d, ip: %s)\n", account_name, (int)new_gm_level, ip);
- }
+ memcpy(WFIFOP(fd,6), acc.userid, 24);
+
+ if (isGM(acc.account_id) == new_gm_level)
+ ShowNotice("'ladmin': Attempt to modify of a GM level, but the GM level is already the good GM level (account: %s (%d), GM level: %d, ip: %s)\n", acc.userid, acc.account_id, (int)new_gm_level, ip);
+ else
+ {
+ //TODO: change level
}
}
+ }
WFIFOSET(fd,30);
RFIFOSKIP(fd,27);
break;
@@ -739,133 +723,131 @@ int parse_admin(int fd)
case 0x7950: // Request to change the validity limite (timestamp) (relative change)
if (RFIFOREST(fd) < 38)
return 0;
- {
- time_t timestamp;
- struct tm *tmtime;
- char tmpstr[2048];
- char tmpstr2[2048];
- WFIFOW(fd,0) = 0x7951;
- WFIFOL(fd,2) = 0xFFFFFFFF; // WTF???
- account_name = (char*)RFIFOP(fd,2);
- account_name[23] = '\0';
- remove_control_chars(account_name);
- i = search_account_index(account_name);
- if (i != -1) {
- WFIFOL(fd,2) = auth_dat[i].account_id;
- memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24);
- timestamp = auth_dat[i].expiration_time;
- if (timestamp == 0 || timestamp < time(NULL))
- timestamp = time(NULL);
- tmtime = localtime(&timestamp);
- tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,26);
- tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,28);
- tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,30);
- tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,32);
- tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,34);
- tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,36);
- timestamp = mktime(tmtime);
- if (timestamp != -1) {
- strftime(tmpstr, 24, login_config.date_format, localtime(&auth_dat[i].expiration_time));
- strftime(tmpstr2, 24, login_config.date_format, localtime(&timestamp));
- ShowNotice("'ladmin': Adjustment of a validity limit (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %d (%s), ip: %s)\n", auth_dat[i].userid, auth_dat[i].expiration_time, (auth_dat[i].expiration_time == 0 ? "unlimited" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), timestamp, (timestamp == 0 ? "unlimited" : tmpstr2), ip);
- auth_dat[i].expiration_time = timestamp;
- mmo_auth_sync();
- WFIFOL(fd,30) = (unsigned long)auth_dat[i].expiration_time;
- } else {
- strftime(tmpstr, 24, login_config.date_format, localtime(&auth_dat[i].expiration_time));
- ShowNotice("'ladmin': Impossible to adjust a validity limit (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> ???, ip: %s)\n", auth_dat[i].userid, auth_dat[i].expiration_time, (auth_dat[i].expiration_time == 0 ? "unlimited" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), ip);
- WFIFOL(fd,30) = 0;
- }
+ {
+ time_t timestamp;
+ struct tm *tmtime;
+ char tmpstr[2048];
+ char tmpstr2[2048];
+ WFIFOW(fd,0) = 0x7951;
+ WFIFOL(fd,2) = 0xFFFFFFFF; // WTF???
+ account_name = (char*)RFIFOP(fd,2);
+ account_name[23] = '\0';
+ remove_control_chars(account_name);
+ i = search_account_index(account_name);
+ if (i != -1) {
+ WFIFOL(fd,2) = auth_dat[i].account_id;
+ memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24);
+ timestamp = auth_dat[i].expiration_time;
+ if (timestamp == 0 || timestamp < time(NULL))
+ timestamp = time(NULL);
+ tmtime = localtime(&timestamp);
+ tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,26);
+ tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,28);
+ tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,30);
+ tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,32);
+ tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,34);
+ tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,36);
+ timestamp = mktime(tmtime);
+ if (timestamp != -1) {
+ strftime(tmpstr, 24, login_config.date_format, localtime(&auth_dat[i].expiration_time));
+ strftime(tmpstr2, 24, login_config.date_format, localtime(&timestamp));
+ ShowNotice("'ladmin': Adjustment of a validity limit (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %d (%s), ip: %s)\n", auth_dat[i].userid, auth_dat[i].expiration_time, (auth_dat[i].expiration_time == 0 ? "unlimited" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), timestamp, (timestamp == 0 ? "unlimited" : tmpstr2), ip);
+ auth_dat[i].expiration_time = timestamp;
+ mmo_auth_sync();
+ WFIFOL(fd,30) = (unsigned long)auth_dat[i].expiration_time;
} else {
- memcpy(WFIFOP(fd,6), account_name, 24);
- ShowNotice("'ladmin': Attempt to adjust the validity limit of an unknown account (account: %s, ip: %s)\n", account_name, ip);
+ strftime(tmpstr, 24, login_config.date_format, localtime(&auth_dat[i].expiration_time));
+ ShowNotice("'ladmin': Impossible to adjust a validity limit (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> ???, ip: %s)\n", auth_dat[i].userid, auth_dat[i].expiration_time, (auth_dat[i].expiration_time == 0 ? "unlimited" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), ip);
WFIFOL(fd,30) = 0;
}
+ } else {
+ memcpy(WFIFOP(fd,6), account_name, 24);
+ ShowNotice("'ladmin': Attempt to adjust the validity limit of an unknown account (account: %s, ip: %s)\n", account_name, ip);
+ WFIFOL(fd,30) = 0;
}
+
WFIFOSET(fd,34);
+ }
RFIFOSKIP(fd,38);
break;
-
+*/
case 0x7952: // Request about informations of an account (by account name)
if (RFIFOREST(fd) < 26)
return 0;
+ {
+ struct mmo_account acc;
+
WFIFOW(fd,0) = 0x7953;
- WFIFOL(fd,2) = 0xFFFFFFFF; // WTF???
+
account_name = (char*)RFIFOP(fd,2);
account_name[23] = '\0';
- remove_control_chars(account_name);
- i = search_account_index(account_name);
- if (i != -1) {
- WFIFOL(fd,2) = auth_dat[i].account_id;
- WFIFOB(fd,6) = (unsigned char)isGM(auth_dat[i].account_id);
- memcpy(WFIFOP(fd,7), auth_dat[i].userid, 24);
- WFIFOB(fd,31) = auth_dat[i].sex;
- WFIFOL(fd,32) = auth_dat[i].logincount;
- WFIFOL(fd,36) = auth_dat[i].state;
- memcpy(WFIFOP(fd,40), auth_dat[i].error_message, 20);
- memcpy(WFIFOP(fd,60), auth_dat[i].lastlogin, 24);
- memcpy(WFIFOP(fd,84), auth_dat[i].last_ip, 16);
- memcpy(WFIFOP(fd,100), auth_dat[i].email, 40);
- WFIFOL(fd,140) = (unsigned long)auth_dat[i].expiration_time;
- WFIFOL(fd,144) = (unsigned long)auth_dat[i].unban_time;
- WFIFOW(fd,148) = (uint16)strlen(auth_dat[i].memo);
- if (auth_dat[i].memo[0]) {
- memcpy(WFIFOP(fd,150), auth_dat[i].memo, strlen(auth_dat[i].memo));
- }
- ShowNotice("'ladmin': Sending information of an account (request by the name; account: %s, id: %d, ip: %s)\n", auth_dat[i].userid, auth_dat[i].account_id, ip);
- WFIFOSET(fd,150+strlen(auth_dat[i].memo));
- } else {
- memcpy(WFIFOP(fd,7), account_name, 24);
- WFIFOW(fd,148) = 0;
+
+ if( accounts->load_str(accounts, &acc, account_name) )
+ {
+ ShowNotice("'ladmin': Sending information of an account (request by the name; account: %s, id: %d, ip: %s)\n", acc.userid, acc.account_id, ip);
+ WFIFOL(fd,2) = acc.account_id;
+ WFIFOB(fd,6) = acc.level;
+ safestrncpy((char*)WFIFOP(fd,7), acc.userid, 24);
+ WFIFOB(fd,31) = acc.sex;
+ WFIFOL(fd,32) = acc.logincount;
+ WFIFOL(fd,36) = acc.state;
+ safestrncpy((char*)WFIFOP(fd,40), "-", 20); // error message (removed)
+ safestrncpy((char*)WFIFOP(fd,60), acc.lastlogin, 24);
+ safestrncpy((char*)WFIFOP(fd,84), acc.last_ip, 16);
+ safestrncpy((char*)WFIFOP(fd,100), acc.email, 40);
+ WFIFOL(fd,140) = (unsigned long)acc.expiration_time;
+ WFIFOL(fd,144) = (unsigned long)acc.unban_time;
+ WFIFOW(fd,148) = 0; // previously, this was strlen(memo), and memo went afterwards
+ }
+ else
+ {
ShowNotice("'ladmin': Attempt to obtain information (by the name) of an unknown account (account: %s, ip: %s)\n", account_name, ip);
- WFIFOSET(fd,150);
+ WFIFOL(fd,2) = -1;
+ safestrncpy((char*)WFIFOP(fd,7), account_name, 24); // not found
}
+
+ WFIFOSET(fd,150);
+ }
RFIFOSKIP(fd,26);
break;
case 0x7954: // Request about information of an account (by account id)
if (RFIFOREST(fd) < 6)
return 0;
+ {
+ struct mmo_account acc;
+
+ int account_id = RFIFOL(fd,2);
+
+ WFIFOHEAD(fd,150);
WFIFOW(fd,0) = 0x7953;
- WFIFOL(fd,2) = RFIFOL(fd,2);
- memset(WFIFOP(fd,7), '\0', 24);
- for(i = 0; i < auth_num; i++) {
- if (auth_dat[i].account_id == (int)RFIFOL(fd,2)) {
- ShowNotice("'ladmin': Sending information of an account (request by the id; account: %s, id: %d, ip: %s)\n", auth_dat[i].userid, RFIFOL(fd,2), ip);
- WFIFOB(fd,6) = (unsigned char)isGM(auth_dat[i].account_id);
- memcpy(WFIFOP(fd,7), auth_dat[i].userid, 24);
- WFIFOB(fd,31) = auth_dat[i].sex;
- WFIFOL(fd,32) = auth_dat[i].logincount;
- WFIFOL(fd,36) = auth_dat[i].state;
- memcpy(WFIFOP(fd,40), auth_dat[i].error_message, 20);
- memcpy(WFIFOP(fd,60), auth_dat[i].lastlogin, 24);
- memcpy(WFIFOP(fd,84), auth_dat[i].last_ip, 16);
- memcpy(WFIFOP(fd,100), auth_dat[i].email, 40);
- WFIFOL(fd,140) = (unsigned long)auth_dat[i].expiration_time;
- WFIFOL(fd,144) = (unsigned long)auth_dat[i].unban_time;
- WFIFOW(fd,148) = (uint16)strlen(auth_dat[i].memo);
- if (auth_dat[i].memo[0]) {
- memcpy(WFIFOP(fd,150), auth_dat[i].memo, strlen(auth_dat[i].memo));
- }
- WFIFOSET(fd,150+strlen(auth_dat[i].memo));
- break;
- }
+ WFIFOL(fd,2) = account_id;
+
+ if( accounts->load_num(accounts, &acc, account_id) )
+ {
+ ShowNotice("'ladmin': Sending information of an account (request by the id; account: %s, id: %d, ip: %s)\n", acc.userid, account_id, ip);
+ WFIFOB(fd,6) = acc.level;
+ safestrncpy((char*)WFIFOP(fd,7), acc.userid, 24);
+ WFIFOB(fd,31) = acc.sex;
+ WFIFOL(fd,32) = acc.logincount;
+ WFIFOL(fd,36) = acc.state;
+ safestrncpy((char*)WFIFOP(fd,40), "-", 20); // error message (removed)
+ safestrncpy((char*)WFIFOP(fd,60), acc.lastlogin, 24);
+ safestrncpy((char*)WFIFOP(fd,84), acc.last_ip, 16);
+ safestrncpy((char*)WFIFOP(fd,100), acc.email, 40);
+ WFIFOL(fd,140) = (unsigned long)acc.expiration_time;
+ WFIFOL(fd,144) = (unsigned long)acc.unban_time;
+ WFIFOW(fd,148) = 0; // previously, this was strlen(memo), and memo went afterwards
}
- if (i == auth_num) {
- ShowNotice("'ladmin': Attempt to obtain information (by the id) of an unknown account (id: %d, ip: %s)\n", RFIFOL(fd,2), ip);
- strncpy((char*)WFIFOP(fd,7), "", 24);
- WFIFOW(fd,148) = 0;
- WFIFOSET(fd,150);
+ else
+ {
+ ShowNotice("'ladmin': Attempt to obtain information (by the id) of an unknown account (id: %d, ip: %s)\n", account_id, ip);
+ safestrncpy((char*)WFIFOP(fd,7), "", 24); // not found
}
- RFIFOSKIP(fd,6);
- break;
- case 0x7955: // Request to reload GM file (no answer)
- ShowStatus("'ladmin': Request to re-load GM configuration file (ip: %s).\n", ip);
- read_gm_account();
- // send GM accounts to all char-servers
- send_GM_accounts(-1);
- RFIFOSKIP(fd,2);
+ WFIFOSET(fd,150);
+ }
+ RFIFOSKIP(fd,6);
break;
default:
diff --git a/src/login/ipban.h b/src/login/ipban.h
new file mode 100644
index 000000000..b2a1a7d9e
--- /dev/null
+++ b/src/login/ipban.h
@@ -0,0 +1,25 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#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
+bool ipban_check(uint32 ip);
+
+// increases failure count for the specified IP
+void ipban_log(uint32 ip);
+
+// parses configuration option
+bool ipban_config_read(const char* key, const char* value);
+
+
+#endif // __IPBAN_H_INCLUDED__
diff --git a/src/login/ipban_sql.c b/src/login/ipban_sql.c
new file mode 100644
index 000000000..911ae23db
--- /dev/null
+++ b/src/login/ipban_sql.c
@@ -0,0 +1,209 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/cbasetypes.h"
+#include "../common/db.h"
+#include "../common/malloc.h"
+#include "../common/sql.h"
+#include "../common/socket.h"
+#include "../common/strlib.h"
+#include "../common/timer.h"
+#include "login.h"
+#include "ipban.h"
+#include <stdlib.h>
+#include <string.h>
+
+// database options
+static char ipban_db_hostname[32] = "127.0.0.1";
+static uint16 ipban_db_port = 3306;
+static char ipban_db_username[32] = "ragnarok";
+static char ipban_db_password[32] = "ragnarok";
+static char ipban_db_database[32] = "ragnarok";
+static char ipban_table[32] = "ipbanlist";
+
+static char log_db_hostname[32] = "127.0.0.1";
+static uint16 log_db_port = 3306;
+static char log_db_username[32] = "ragnarok";
+static char log_db_password[32] = "ragnarok";
+static char log_db_database[32] = "ragnarok";
+static char loginlog_table[32] = "loginlog";
+
+static char default_codepage[32] = "";
+
+// globals
+static Sql* sql_handle;
+static Sql* logsql_handle;
+static int cleanup_timer_id = INVALID_TIMER;
+
+int ipban_cleanup(int tid, unsigned int tick, int id, intptr data);
+
+
+// initialize
+void ipban_init(void)
+{
+ // establish connections
+ sql_handle = Sql_Malloc();
+ if( SQL_ERROR == Sql_Connect(sql_handle, ipban_db_username, ipban_db_password, ipban_db_hostname, ipban_db_port, ipban_db_database) )
+ {
+ Sql_ShowDebug(sql_handle);
+ Sql_Free(sql_handle);
+ exit(EXIT_FAILURE);
+ }
+ if( default_codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(sql_handle, default_codepage) )
+ Sql_ShowDebug(sql_handle);
+
+ logsql_handle = Sql_Malloc();
+ if( SQL_ERROR == Sql_Connect(logsql_handle, log_db_username, log_db_password, log_db_hostname, log_db_port, log_db_database) )
+ {
+ Sql_ShowDebug(logsql_handle);
+ Sql_Free(logsql_handle);
+ exit(EXIT_FAILURE);
+ }
+ if( default_codepage[0] != '\0' && SQL_ERROR == Sql_SetEncoding(logsql_handle, default_codepage) )
+ Sql_ShowDebug(logsql_handle);
+
+ // 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, 60*1000);
+}
+
+// finalize
+void ipban_final(void)
+{
+ // release data
+ delete_timer(cleanup_timer_id, ipban_cleanup);
+
+ // close connections
+ Sql_Free(sql_handle);
+ sql_handle = NULL;
+ Sql_Free(logsql_handle);
+ logsql_handle = NULL;
+}
+
+// load configuration options
+bool ipban_config_read(const char* key, const char* value)
+{
+ // login server settings
+ if( strcmpi(key, "ipban.enable") == 0 )
+ login_config.ipban = (bool)config_switch(value);
+ else
+ if( strcmpi(key, "ipban.dynamic_pass_failure_ban") == 0 )
+ login_config.dynamic_pass_failure_ban = (bool)config_switch(value);
+ else
+ if( strcmpi(key, "ipban.dynamic_pass_failure_ban_interval") == 0 )
+ login_config.dynamic_pass_failure_ban_interval = atoi(value);
+ else
+ if( strcmpi(key, "ipban.dynamic_pass_failure_ban_limit") == 0 )
+ login_config.dynamic_pass_failure_ban_limit = atoi(value);
+ else
+ if( strcmpi(key, "ipban.dynamic_pass_failure_ban_duration") == 0 )
+ login_config.dynamic_pass_failure_ban_duration = atoi(value);
+ else
+
+ // ipban table settings
+ if( strcmpi(key, "ipban.sql.db_hostname") == 0 )
+ safestrncpy(ipban_db_hostname, value, sizeof(ipban_db_hostname));
+ else
+ if( strcmpi(key, "ipban.sql.db_port") == 0 )
+ ipban_db_port = (uint16)strtoul(value, NULL, 10);
+ else
+ if( strcmpi(key, "ipban.sql.db_username") == 0 )
+ safestrncpy(ipban_db_username, value, sizeof(ipban_db_username));
+ else
+ if( strcmpi(key, "ipban.sql.db_password") == 0 )
+ safestrncpy(ipban_db_password, value, sizeof(ipban_db_password));
+ else
+ if( strcmpi(key, "ipban.sql.db_database") == 0 )
+ safestrncpy(ipban_db_database, value, sizeof(ipban_db_database));
+ else
+ if( strcmpi(key, "ipban.sql.ipban_table") == 0 )
+ safestrncpy(ipban_table, value, sizeof(ipban_table));
+ else
+
+ // interserver settings
+ if( strcmpi(key, "log_db_ip") == 0 )
+ safestrncpy(log_db_hostname, value, sizeof(log_db_hostname));
+ else
+ if( strcmpi(key, "log_db_port") == 0 )
+ log_db_port = (uint16)strtoul(value, NULL, 10);
+ else
+ if( strcmpi(key, "log_db_id") == 0 )
+ safestrncpy(log_db_username, value, sizeof(log_db_username));
+ else
+ if( strcmpi(key, "log_db_pw") == 0 )
+ safestrncpy(log_db_password, value, sizeof(log_db_password));
+ else
+ if( strcmpi(key, "log_db") == 0 )
+ safestrncpy(log_db_database, value, sizeof(log_db_database));
+ else
+ if( strcmpi(key, "loginlog_db") == 0 )
+ safestrncpy(loginlog_table, value, sizeof(loginlog_table));
+ else
+ if( strcmpi(key, "default_codepage") == 0 )
+ safestrncpy(default_codepage, value, sizeof(default_codepage));
+ else
+ return false;
+
+ return true;
+}
+
+// check ip against active bans list
+bool ipban_check(uint32 ip)
+{
+ uint8* p = (uint8*)&ip;
+ char* data = NULL;
+ int matches;
+
+ if( SQL_ERROR == Sql_Query(sql_handle, "SELECT count(*) FROM `%s` WHERE `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;
+ }
+
+ 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 = 0;
+ if( SQL_ERROR == Sql_Query(logsql_handle, "SELECT count(*) FROM `%s` WHERE `ip` = '%s' AND `rcode` = '1' AND `time` > NOW() - INTERVAL %d MINUTE",
+ loginlog_table, ip2str(ip,NULL), login_config.dynamic_pass_failure_ban_interval) )// how many times failed account? in one ip.
+ Sql_ShowDebug(sql_handle);
+
+ //check query result
+ if( SQL_SUCCESS == Sql_NextRow(logsql_handle) )
+ {
+ char* data;
+ Sql_GetData(logsql_handle, 0, &data, NULL);
+ failures = strtoul(data, NULL, 10);
+ Sql_FreeResult(logsql_handle);
+ }
+
+ // 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);
+ }
+}
+
+// remove expired bans
+int ipban_cleanup(int tid, unsigned int tick, int id, intptr data)
+{
+ if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `ipbanlist` WHERE `rtime` <= NOW()") )
+ Sql_ShowDebug(sql_handle);
+
+ return 0;
+}
diff --git a/src/login/ipban_txt.c b/src/login/ipban_txt.c
new file mode 100644
index 000000000..6fee15c28
--- /dev/null
+++ b/src/login/ipban_txt.c
@@ -0,0 +1,50 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/cbasetypes.h"
+#include "../common/strlib.h"
+#include "login.h"
+#include "ipban.h"
+#include <stdlib.h>
+#include <string.h>
+
+void ipban_init(void)
+{
+}
+
+void ipban_final(void)
+{
+}
+
+bool ipban_check(uint32 ip)
+{
+ return false;
+}
+
+void ipban_log(uint32 ip)
+{
+}
+
+bool ipban_config_read(const char* key, const char* value)
+{
+ // login server settings
+ if( strcmpi(key, "ipban.enable") == 0 )
+ login_config.ipban = (bool)config_switch(value);
+ else
+ if( strcmpi(key, "ipban.dynamic_pass_failure_ban") == 0 )
+ login_config.dynamic_pass_failure_ban = (bool)config_switch(value);
+ else
+ if( strcmpi(key, "ipban.dynamic_pass_failure_ban_interval") == 0 )
+ login_config.dynamic_pass_failure_ban_interval = atoi(value);
+ else
+ if( strcmpi(key, "ipban.dynamic_pass_failure_ban_limit") == 0 )
+ login_config.dynamic_pass_failure_ban_limit = atoi(value);
+ else
+ if( strcmpi(key, "ipban.dynamic_pass_failure_ban_duration") == 0 )
+ login_config.dynamic_pass_failure_ban_duration = atoi(value);
+ else
+ return false;
+
+ return true;
+}
+
diff --git a/src/login/login.c b/src/login/login.c
index b26256570..2db051807 100644
--- a/src/login/login.c
+++ b/src/login/login.c
@@ -1,25 +1,25 @@
// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
// For more information, see LICENCE in the main folder
-#include "../common/cbasetypes.h"
-#include "../common/mmo.h"
#include "../common/core.h"
-#include "../common/socket.h"
#include "../common/db.h"
-#include "../common/timer.h"
#include "../common/malloc.h"
-#include "../common/strlib.h"
+#include "../common/md5calc.h"
#include "../common/showmsg.h"
+#include "../common/socket.h"
+#include "../common/strlib.h"
+#include "../common/timer.h"
#include "../common/version.h"
-#include "../common/md5calc.h"
-#include "../common/lock.h"
+#include "account.h"
+#include "ipban.h"
#include "login.h"
+#include "loginlog.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/stat.h> // for stat/lstat/fstat
+bool ladmin_auth(struct login_session_data* sd, const char* ip);
struct Login_Config login_config;
int login_fd; // login server socket
@@ -29,6 +29,42 @@ struct mmo_char_server server[MAX_SERVERS]; // char server data
#define sex_num2str(num) ( (num == 0 ) ? 'F' : (num == 1 ) ? 'M' : 'S' )
#define sex_str2num(str) ( (str == 'F' ) ? 0 : (str == 'M' ) ? 1 : 2 )
+// Account engines available
+static struct{
+ AccountDB* (*constructor)(void);
+ AccountDB* db;
+} account_engines[] = {
+#ifdef WITH_TXT
+ {account_db_txt, NULL},
+#endif
+#ifdef WITH_SQL
+ {account_db_sql, NULL},
+#endif
+#ifdef ACCOUNTDB_ENGINE_0
+ {ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_0), NULL},
+#endif
+#ifdef ACCOUNTDB_ENGINE_1
+ {ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_1), NULL},
+#endif
+#ifdef ACCOUNTDB_ENGINE_2
+ {ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_2), NULL},
+#endif
+#ifdef ACCOUNTDB_ENGINE_3
+ {ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_3), NULL},
+#endif
+#ifdef ACCOUNTDB_ENGINE_4
+ {ACCOUNTDB_CONSTRUCTOR(ACCOUNTDB_ENGINE_4), NULL},
+#endif
+ // end of structure
+ {NULL, NULL}
+};
+// 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;
@@ -38,45 +74,6 @@ struct s_subnet {
int subnet_count = 0;
-// GM account management
-struct gm_account* gm_account_db = NULL;
-unsigned int GM_num = 0; // number of gm accounts
-char GM_account_filename[1024] = "conf/GM_account.txt";
-long creation_time_GM_account_file; // tracks the last-changed timestamp of the gm accounts file
-int gm_account_filename_check_timer = 15; // Timer to check if GM_account file has been changed and reload GM account automaticaly (in seconds; default: 15)
-
-//Account registration flood protection [Kevin]
-int allowed_regs = 1;
-int time_allowed = 10; //in seconds
-unsigned int new_reg_tick = 0;
-
-
-// data handling (TXT)
-char account_filename[1024] = "save/account.txt";
-
-// account database
-struct mmo_account* auth_dat = NULL;
-unsigned int auth_num = 0, auth_max = 0;
-
-int account_id_count = START_ACCOUNT_NUM;
-
-// define the number of times that some players must authentify them before to save account file.
-// it's just about normal authentication. If an account is created or modified, save is immediatly done.
-// An authentication just change last connected IP and date. It already save in log file.
-// set minimum auth change before save:
-#define AUTH_BEFORE_SAVE_FILE 10
-// set divider of auth_num to found number of change before save
-#define AUTH_SAVE_FILE_DIVIDER 50
-int auth_before_save_file = 0; // Counter. First save when 1st char-server do connection.
-
-
-// ladmin configuration
-bool admin_state = false;
-char admin_pass[24] = "";
-uint32 admin_allowed_ip = 0;
-
-int parse_admin(int fd);
-
//-----------------------------------------------------
// Auth database
@@ -84,6 +81,7 @@ int parse_admin(int fd);
#define AUTH_TIMEOUT 30000
struct auth_node {
+
int account_id;
uint32 login_id1;
uint32 login_id2;
@@ -93,11 +91,12 @@ struct auth_node {
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;
@@ -157,582 +156,61 @@ static int waiting_disconnect_timer(int tid, unsigned int tick, int id, intptr d
return 0;
}
-//--------------------------------------------------------------------
-// Packet send to all char-servers, except one (wos: without our self)
-//--------------------------------------------------------------------
-int charif_sendallwos(int sfd, uint8* buf, size_t len)
+static int online_db_setoffline(DBKey key, void* data, va_list ap)
{
- int i, c;
-
- for( i = 0, c = 0; i < MAX_SERVERS; ++i )
+ struct online_login_data* p = (struct online_login_data*)data;
+ int server = va_arg(ap, int);
+ if( server == -1 )
{
- int fd = server[i].fd;
- if( session_isValid(fd) && fd != sfd )
+ p->char_server = -1;
+ if( p->waiting_disconnect != -1 )
{
- WFIFOHEAD(fd,len);
- memcpy(WFIFOP(fd,0), buf, len);
- WFIFOSET(fd,len);
- ++c;
- }
- }
-
- return c;
-}
-
-//----------------------------------------------------------------------
-// Determine if an account (id) is a GM account
-// and returns its level (or 0 if it isn't a GM account or if not found)
-//----------------------------------------------------------------------
-int isGM(int account_id)
-{
- unsigned int i;
- ARR_FIND( 0, GM_num, i, gm_account_db[i].account_id == account_id );
- return ( i < GM_num ) ? gm_account_db[i].level : 0;
-}
-
-//----------------------------------------------------------------------
-// Adds a new GM using acc id and level
-//----------------------------------------------------------------------
-void addGM(int account_id, int level)
-{
- static unsigned int GM_max = 0;
- unsigned int i;
-
- ARR_FIND( 0, auth_num, i, auth_dat[i].account_id == account_id );
- if( i == auth_num )
- return; // no such account
-
- ARR_FIND( 0, GM_num, i, gm_account_db[i].account_id == account_id );
- if( i < GM_num )
- {
- if (gm_account_db[i].level == level)
- ShowWarning("addGM: GM account %d defined twice (same level: %d).\n", account_id, level);
- else {
- ShowWarning("addGM: GM account %d defined twice (levels: %d and %d).\n", account_id, gm_account_db[i].level, level);
- gm_account_db[i].level = level;
- }
- return; // entry already present
- }
-
- // new account
- if (GM_num >= GM_max) {
- GM_max += 256;
- RECREATE(gm_account_db, struct gm_account, GM_max);
- }
- gm_account_db[GM_num].account_id = account_id;
- gm_account_db[GM_num].level = level;
- GM_num++;
- if (GM_num >= 4000)
- ShowWarning("4000 GM accounts found. Next GM accounts are not read.\n");
-}
-
-//-------------------------------------------------------
-// Reading function of GM accounts file (and their level)
-//-------------------------------------------------------
-int read_gm_account(void)
-{
- char line[512];
- FILE *fp;
- int account_id, level;
- int line_counter;
- struct stat file_stat;
- int start_range = 0, end_range = 0, is_range = 0, current_id = 0;
-
- if(gm_account_db) aFree(gm_account_db);
- CREATE(gm_account_db, struct gm_account, 1);
- GM_num = 0;
-
- // get last modify time/date
- if (stat(GM_account_filename, &file_stat))
- creation_time_GM_account_file = 0; // error
- else
- creation_time_GM_account_file = (long)file_stat.st_mtime;
-
- if ((fp = fopen(GM_account_filename, "r")) == NULL) {
- ShowError("read_gm_account: GM accounts file [%s] not found.\n", GM_account_filename);
- return 1;
- }
-
- line_counter = 0;
- // limited to 4000, because we send information to char-servers (more than 4000 GM accounts???)
- // int (id) + int (level) = 8 bytes * 4000 = 32k (limit of packets in windows)
- while(fgets(line, sizeof(line), fp) && GM_num < 4000)
- {
- line_counter++;
- if ((line[0] == '/' && line[1] == '/') || line[0] == '\0' || line[0] == '\n' || line[0] == '\r')
- continue;
- is_range = (sscanf(line, "%d%*[-~]%d %d",&start_range,&end_range,&level)==3); // ID Range [MC Cameri]
- if (!is_range && sscanf(line, "%d %d", &account_id, &level) != 2 && sscanf(line, "%d: %d", &account_id, &level) != 2)
- ShowError("read_gm_account: file [%s], invalid 'acount_id|range level' format (line #%d).\n", GM_account_filename, line_counter);
- else if (level <= 0)
- ShowError("read_gm_account: file [%s] %dth account (line #%d) (invalid level [0 or negative]: %d).\n", GM_account_filename, GM_num+1, line_counter, level);
- else {
- if (level > 99) {
- ShowNotice("read_gm_account: file [%s] %dth account (invalid level, but corrected: %d->99).\n", GM_account_filename, GM_num+1, level);
- level = 99;
- }
- if (is_range) {
- if (start_range==end_range)
- ShowError("read_gm_account: file [%s] invalid range, beginning of range is equal to end of range (line #%d).\n", GM_account_filename, line_counter);
- else if (start_range>end_range)
- ShowError("read_gm_account: file [%s] invalid range, beginning of range must be lower than end of range (line #%d).\n", GM_account_filename, line_counter);
- else
- for (current_id = start_range;current_id<=end_range;current_id++)
- addGM(current_id,level);
- } else {
- addGM(account_id,level);
- }
+ delete_timer(p->waiting_disconnect, waiting_disconnect_timer);
+ p->waiting_disconnect = -1;
}
}
- fclose(fp);
-
- ShowStatus("read_gm_account: file '%s' read (%d GM accounts found).\n", GM_account_filename, GM_num);
-
+ else if( p->char_server == server )
+ p->char_server = -2; //Char server disconnected.
return 0;
}
-
-//-----------------------------------------------
-// Search an account id
-// (return account index or -1 (if not found))
-// If exact account name is not found,
-// the function checks without case sensitive
-// and returns index if only 1 account is found
-// and similar to the searched name.
-//-----------------------------------------------
-int search_account_index(char* account_name)
+static int online_data_cleanup_sub(DBKey key, void *data, va_list ap)
{
- unsigned int i, quantity;
- int index;
-
- quantity = 0;
- index = -1;
-
- for(i = 0; i < auth_num; i++) {
- // Without case sensitive check (increase the number of similar account names found)
- if (stricmp(auth_dat[i].userid, account_name) == 0) {
- // Strict comparison (if found, we finish the function immediatly with correct value)
- if (strcmp(auth_dat[i].userid, account_name) == 0)
- return i;
- quantity++;
- index = i;
- }
- }
- // Here, the exact account name is not found
- // We return the found index of a similar account ONLY if there is 1 similar account
- if (quantity == 1)
- return index;
-
- // Exact account name is not found and 0 or more than 1 similar accounts have been found ==> we say not found
- return -1;
+ struct online_login_data *character= (struct online_login_data*)data;
+ if (character->char_server == -2) //Unknown server.. set them offline
+ remove_online_user(character->account_id);
+ return 0;
}
-//--------------------------------------------------------
-// Create a string to save the account in the account file
-//--------------------------------------------------------
-int mmo_auth_tostr(char* str, struct mmo_account* p)
+static int online_data_cleanup(int tid, unsigned int tick, int id, intptr data)
{
- int i;
- char *str_p = str;
-
- str_p += sprintf(str_p, "%d\t%s\t%s\t%s\t%c\t%d\t%u\t%s\t%s\t%ld\t%s\t%s\t%ld\t",
- p->account_id, p->userid, p->pass, p->lastlogin, p->sex,
- p->logincount, p->state, p->email, p->error_message,
- (long)p->expiration_time, p->last_ip, p->memo, (long)p->unban_time);
-
- for(i = 0; i < p->account_reg2_num; i++)
- if (p->account_reg2[i].str[0])
- str_p += sprintf(str_p, "%s,%s ", p->account_reg2[i].str, p->account_reg2[i].value);
-
+ online_db->foreach(online_db, online_data_cleanup_sub);
return 0;
-}
+}
-//---------------------------------
-// Reading of the accounts database
-//---------------------------------
-int mmo_auth_init(void)
+
+//--------------------------------------------------------------------
+// Packet send to all char-servers, except one (wos: without our self)
+//--------------------------------------------------------------------
+int charif_sendallwos(int sfd, uint8* buf, size_t len)
{
- FILE *fp;
- int account_id;
- uint32 state;
- int logincount, n;
- uint32 i, j;
- char line[2048], *p, userid[2048], pass[2048], lastlogin[2048], sex, email[2048], error_message[2048], last_ip[2048], memo[2048];
- long unban_time;
- long expiration_time;
- char str[2048];
- char v[2048];
- int GM_count = 0;
- int server_count = 0;
-
- auth_max = 256;
- CREATE(auth_dat, struct mmo_account, auth_max);
-
- if ((fp = fopen(account_filename, "r")) == NULL) {
- // no account file -> no account -> no login, including char-server (ERROR)
- ShowError(CL_RED"mmmo_auth_init: Accounts file [%s] not found."CL_RESET"\n", account_filename);
- return 0;
- }
+ int i, c;
- while(fgets(line, sizeof(line), fp) != NULL)
+ for( i = 0, c = 0; i < MAX_SERVERS; ++i )
{
- if (line[0] == '/' && line[1] == '/')
- continue;
-
- p = line;
-
- memset(userid, 0, sizeof(userid));
- memset(pass, 0, sizeof(pass));
- memset(lastlogin, 0, sizeof(lastlogin));
- memset(email, 0, sizeof(email));
- memset(error_message, 0, sizeof(error_message));
- memset(last_ip, 0, sizeof(last_ip));
- memset(memo, 0, sizeof(memo));
-
- // database version reading (v2)
- if (((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%u\t"
- "%[^\t]\t%[^\t]\t%ld\t%[^\t]\t%[^\t]\t%ld%n",
- &account_id, userid, pass, lastlogin, &sex, &logincount, &state,
- email, error_message, &expiration_time, last_ip, memo, &unban_time, &n)) == 13 && line[n] == '\t') ||
- ((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%u\t"
- "%[^\t]\t%[^\t]\t%ld\t%[^\t]\t%[^\t]%n",
- &account_id, userid, pass, lastlogin, &sex, &logincount, &state,
- email, error_message, &expiration_time, last_ip, memo, &n)) == 12 && line[n] == '\t')) {
- n = n + 1;
-
- // Some checks
- if (account_id > END_ACCOUNT_NUM) {
- ShowError(CL_RED"mmmo_auth_init: an account has an id higher than %d\n", END_ACCOUNT_NUM);
- ShowError(" account id #%d -> account not read (data is lost!)."CL_RESET"\n", account_id);
- continue;
- }
- userid[23] = '\0';
- remove_control_chars(userid);
- for(j = 0; j < auth_num; j++) {
- if (auth_dat[j].account_id == account_id) {
- ShowError(CL_RED"mmmo_auth_init: an account has an identical id to another.\n");
- ShowError(" account id #%d -> new account not read (data is lost!)."CL_RED"\n", account_id);
- break;
- } else if (strcmp(auth_dat[j].userid, userid) == 0) {
- ShowError(CL_RED"mmmo_auth_init: account name already exists.\n");
- ShowError(" account name '%s' -> new account not read (data is lost!)."CL_RESET"\n", userid); // 2 lines, account name can be long.
- break;
- }
- }
- if (j != auth_num)
- continue;
-
- if (auth_num >= auth_max) {
- auth_max += 256;
- auth_dat = (struct mmo_account*)aRealloc(auth_dat, sizeof(struct mmo_account) * auth_max);
- }
-
- memset(&auth_dat[auth_num], '\0', sizeof(struct mmo_account));
-
- auth_dat[auth_num].account_id = account_id;
-
- strncpy(auth_dat[auth_num].userid, userid, 24);
-
- pass[32] = '\0';
- remove_control_chars(pass);
- strncpy(auth_dat[auth_num].pass, pass, 32);
-
- lastlogin[23] = '\0';
- remove_control_chars(lastlogin);
- strncpy(auth_dat[auth_num].lastlogin, lastlogin, 24);
-
- auth_dat[auth_num].sex = sex;
-
- if (logincount >= 0)
- auth_dat[auth_num].logincount = logincount;
- else
- auth_dat[auth_num].logincount = 0;
-
- if (state > 255)
- auth_dat[auth_num].state = 100;
- else
- auth_dat[auth_num].state = state;
-
- if (e_mail_check(email) == 0) {
- ShowNotice("Account %s (%d): invalid e-mail (replaced par a@a.com).\n", auth_dat[auth_num].userid, auth_dat[auth_num].account_id);
- strncpy(auth_dat[auth_num].email, "a@a.com", 40);
- } else {
- remove_control_chars(email);
- strncpy(auth_dat[auth_num].email, email, 40);
- }
-
- error_message[19] = '\0';
- remove_control_chars(error_message);
- if (error_message[0] == '\0' || state != 7) { // 7, because state is packet 0x006a value + 1
- strncpy(auth_dat[auth_num].error_message, "-", 20);
- } else {
- strncpy(auth_dat[auth_num].error_message, error_message, 20);
- }
-
- if (i == 13)
- auth_dat[auth_num].unban_time = (time_t)unban_time;
- else
- auth_dat[auth_num].unban_time = 0;
-
- auth_dat[auth_num].expiration_time = (time_t)expiration_time;
-
- last_ip[15] = '\0';
- remove_control_chars(last_ip);
- strncpy(auth_dat[auth_num].last_ip, last_ip, 16);
-
- memo[254] = '\0';
- remove_control_chars(memo);
- strncpy(auth_dat[auth_num].memo, memo, 255);
-
- for(j = 0; j < ACCOUNT_REG2_NUM; j++) {
- p += n;
- if (sscanf(p, "%[^\t,],%[^\t ] %n", str, v, &n) != 2) {
- // We must check if a str is void. If it's, we can continue to read other REG2.
- // Account line will have something like: str2,9 ,9 str3,1 (here, ,9 is not good)
- if (p[0] == ',' && sscanf(p, ",%[^\t ] %n", v, &n) == 1) {
- j--;
- continue;
- } else
- break;
- }
- str[31] = '\0';
- remove_control_chars(str);
- strncpy(auth_dat[auth_num].account_reg2[j].str, str, 32);
- strncpy(auth_dat[auth_num].account_reg2[j].value,v,256);
- }
- auth_dat[auth_num].account_reg2_num = j;
-
- if (isGM(account_id) > 0)
- GM_count++;
- if (auth_dat[auth_num].sex == 'S')
- server_count++;
-
- auth_num++;
- if (account_id >= account_id_count)
- account_id_count = account_id + 1;
-
- // Old athena database version reading (v1)
- } else if ((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%u\t%n",
- &account_id, userid, pass, lastlogin, &sex, &logincount, &state, &n)) >= 5) {
- if (account_id > END_ACCOUNT_NUM) {
- ShowError(CL_RED"mmmo_auth_init: an account has an id higher than %d\n", END_ACCOUNT_NUM);
- ShowError(" account id #%d -> account not read (data is lost!)."CL_RESET"\n", account_id);
- continue;
- }
- userid[23] = '\0';
- remove_control_chars(userid);
- for(j = 0; j < auth_num; j++) {
- if (auth_dat[j].account_id == account_id) {
- ShowError(CL_RED"mmo_auth_init: an account has an identical id to another.\n");
- ShowError(" account id #%d -> new account not read (data is lost!)."CL_RESET"\n", account_id);
- break;
- } else if (strcmp(auth_dat[j].userid, userid) == 0) {
- ShowError(CL_RED"mmo_auth_init: account name already exists.\n");
- ShowError(" account name '%s' -> new account not read (data is lost!)."CL_RESET"\n", userid);
- break;
- }
- }
- if (j != auth_num)
- continue;
-
- if (auth_num >= auth_max) {
- auth_max += 256;
- RECREATE(auth_dat, struct mmo_account, auth_max);
- }
-
- memset(&auth_dat[auth_num], '\0', sizeof(struct mmo_account));
-
- auth_dat[auth_num].account_id = account_id;
-
- strncpy(auth_dat[auth_num].userid, userid, 24);
-
- pass[23] = '\0';
- remove_control_chars(pass);
- strncpy(auth_dat[auth_num].pass, pass, 24);
-
- lastlogin[23] = '\0';
- remove_control_chars(lastlogin);
- strncpy(auth_dat[auth_num].lastlogin, lastlogin, 24);
-
- auth_dat[auth_num].sex = sex;
-
- if (i >= 6) {
- if (logincount >= 0)
- auth_dat[auth_num].logincount = logincount;
- else
- auth_dat[auth_num].logincount = 0;
- } else
- auth_dat[auth_num].logincount = 0;
-
- if (i >= 7) {
- if (state > 255)
- auth_dat[auth_num].state = 100;
- else
- auth_dat[auth_num].state = state;
- } else
- auth_dat[auth_num].state = 0;
-
- // Initialization of new data
- strncpy(auth_dat[auth_num].email, "a@a.com", 40);
- strncpy(auth_dat[auth_num].error_message, "-", 20);
- auth_dat[auth_num].unban_time = 0;
- auth_dat[auth_num].expiration_time = 0;
- strncpy(auth_dat[auth_num].last_ip, "-", 16);
- strncpy(auth_dat[auth_num].memo, "-", 255);
-
- for(j = 0; j < ACCOUNT_REG2_NUM; j++) {
- p += n;
- if (sscanf(p, "%[^\t,],%[^\t ] %n", str, v, &n) != 2) {
- // We must check if a str is void. If it's, we can continue to read other REG2.
- // Account line will have something like: str2,9 ,9 str3,1 (here, ,9 is not good)
- if (p[0] == ',' && sscanf(p, ",%[^\t ] %n", v, &n) == 1) {
- j--;
- continue;
- } else
- break;
- }
- str[31] = '\0';
- remove_control_chars(str);
- strncpy(auth_dat[auth_num].account_reg2[j].str, str, 32);
- strncpy(auth_dat[auth_num].account_reg2[j].value,v,256);
- }
- auth_dat[auth_num].account_reg2_num = j;
-
- if (isGM(account_id) > 0)
- GM_count++;
- if (auth_dat[auth_num].sex == 'S')
- server_count++;
-
- auth_num++;
- if (account_id >= account_id_count)
- account_id_count = account_id + 1;
-
- } else {
- int i = 0;
- if (sscanf(line, "%d\t%%newid%%\n%n", &account_id, &i) == 1 &&
- i > 0 && account_id > account_id_count)
- account_id_count = account_id;
- }
- }
- fclose(fp);
-
- if( auth_num == 0 )
- ShowNotice("mmo_auth_init: No account found in %s.\n", account_filename);
- else
- if( auth_num == 1 )
- ShowStatus("mmo_auth_init: 1 account read in %s,\n", account_filename);
- else
- ShowStatus("mmo_auth_init: %d accounts read in %s,\n", auth_num, account_filename);
-
- if( GM_count == 0 )
- ShowStatus(" of which is no GM account, and \n");
- else
- if( GM_count == 1 )
- ShowStatus(" of which is 1 GM account, and \n");
- else
- ShowStatus(" of which is %d GM accounts, and \n", GM_count);
-
- if( server_count == 0 )
- ShowStatus(" no server account ('S').\n");
- else
- if( server_count == 1 )
- ShowStatus(" 1 server account ('S').\n");
- else
- ShowStatus(" %d server accounts ('S').\n", server_count);
-
- return 0;
-}
-
-//------------------------------------------
-// Writing of the accounts database file
-// (accounts are sorted by id before save)
-//------------------------------------------
-void mmo_auth_sync(void)
-{
- FILE *fp;
- unsigned int i, j, k;
- int lock;
- int account_id;
- CREATE_BUFFER(id, int, auth_num);
- char line[65536];
-
- // Sorting before save
- for(i = 0; i < auth_num; i++) {
- id[i] = i;
- account_id = auth_dat[i].account_id;
- for(j = 0; j < i; j++) {
- if (account_id < auth_dat[id[j]].account_id) {
- for(k = i; k > j; k--)
- id[k] = id[k-1];
- id[j] = i; // id[i]
- break;
- }
+ int fd = server[i].fd;
+ if( session_isValid(fd) && fd != sfd )
+ {
+ WFIFOHEAD(fd,len);
+ memcpy(WFIFOP(fd,0), buf, len);
+ WFIFOSET(fd,len);
+ ++c;
}
}
- // Data save
- if ((fp = lock_fopen(account_filename, &lock)) == NULL) {
- //if (id) aFree(id);
- DELETE_BUFFER(id);
- return;
- }
-
- fprintf(fp, "// Accounts file: here are saved all information about the accounts.\n");
- fprintf(fp, "// Structure: ID, account name, password, last login time, sex, # of logins, state, email, error message for state 7, validity time, last (accepted) login ip, memo field, ban timestamp, repeated(register text, register value)\n");
- fprintf(fp, "// Some explanations:\n");
- fprintf(fp, "// account name : between 4 to 23 char for a normal account (standard client can't send less than 4 char).\n");
- fprintf(fp, "// account password: between 4 to 23 char\n");
- fprintf(fp, "// sex : M or F for normal accounts, S for server accounts\n");
- fprintf(fp, "// state : 0: account is ok, 1 to 256: error code of packet 0x006a + 1\n");
- fprintf(fp, "// email : between 3 to 39 char (a@a.com is like no email)\n");
- fprintf(fp, "// error message : text for the state 7: 'Your are Prohibited to login until <text>'. Max 19 char\n");
- fprintf(fp, "// valitidy time : 0: unlimited account, <other value>: date calculated by addition of 1/1/1970 + value (number of seconds since the 1/1/1970)\n");
- fprintf(fp, "// memo field : max 254 char\n");
- fprintf(fp, "// ban time : 0: no ban, <other value>: banned until the date: date calculated by addition of 1/1/1970 + value (number of seconds since the 1/1/1970)\n");
- for(i = 0; i < auth_num; i++) {
- k = id[i]; // use of sorted index
- if (auth_dat[k].account_id == -1)
- continue;
-
- mmo_auth_tostr(line, &auth_dat[k]);
- fprintf(fp, "%s\n", line);
- }
- fprintf(fp, "%d\t%%newid%%\n", account_id_count);
-
- lock_fclose(fp, account_filename, &lock);
-
- // set new counter to minimum number of auth before save
- auth_before_save_file = auth_num / AUTH_SAVE_FILE_DIVIDER; // Re-initialise counter. We have save.
- if (auth_before_save_file < AUTH_BEFORE_SAVE_FILE)
- auth_before_save_file = AUTH_BEFORE_SAVE_FILE;
-
- //if (id) aFree(id);
- DELETE_BUFFER(id);
-
- return;
+ return c;
}
-//-----------------------------------------------------
-// Check if we must save accounts file or not
-// every minute, we check if we must save because we
-// have do some authentications without arrive to
-// the minimum of authentications for the save.
-// Note: all other modification of accounts (deletion,
-// change of some informations excepted lastip/
-// lastlogintime, creation) are always save
-// immediatly and set the minimum of
-// authentications to its initialization value.
-//-----------------------------------------------------
-int check_auth_sync(int tid, unsigned int tick, int id, intptr data)
-{
- // we only save if necessary:
- // we have do some authentications without do saving
- if (auth_before_save_file < AUTH_BEFORE_SAVE_FILE ||
- auth_before_save_file < (int)(auth_num / AUTH_SAVE_FILE_DIVIDER))
- mmo_auth_sync();
-
- return 0;
-}
//-----------------------------------------------------
// periodic ip address synchronization
@@ -746,345 +224,148 @@ static int sync_ip_addresses(int tid, unsigned int tick, int id, intptr data)
return 0;
}
-//-----------------------------------------------------
-// Send GM accounts to one or all char-servers
-//-----------------------------------------------------
-void send_GM_accounts(int fd)
-{
- unsigned int i;
- uint8 buf[32767];
- uint16 len;
-
- len = 4;
- WBUFW(buf,0) = 0x2732;
- for(i = 0; i < GM_num; i++)
- // send only existing accounts. We can not create a GM account when server is online.
- if (gm_account_db[i].level > 0) {
- WBUFL(buf,len) = gm_account_db[i].account_id;
- WBUFB(buf,len+4) = (uint8)gm_account_db[i].level;
- len += 5;
- if (len >= 32000) {
- ShowWarning("send_GM_accounts: Too many accounts! Only %d out of %d were sent.\n", i, GM_num);
- break;
- }
- }
-
- WBUFW(buf,2) = len;
- if (fd == -1) // send to all charservers
- charif_sendallwos(-1, buf, len);
- else { // send only to target
- WFIFOHEAD(fd,len);
- memcpy(WFIFOP(fd,0), buf, len);
- WFIFOSET(fd,len);
- }
-
- return;
-}
//-----------------------------------------------------
-// Check if GM file account have been changed
-//-----------------------------------------------------
-int check_GM_file(int tid, unsigned int tick, int id, intptr data)
-{
- struct stat file_stat;
- long new_time;
-
- // if we would not check
- if (gm_account_filename_check_timer < 1)
- return 0;
-
- // get last modify time/date
- if (stat(GM_account_filename, &file_stat))
- new_time = 0; // error
- else
- new_time = (long)file_stat.st_mtime;
-
- if (new_time != creation_time_GM_account_file) {
- read_gm_account();
- send_GM_accounts(-1);
- }
-
- return 0;
-}
-
-
-//-----------------------------------------------------
-// encrypted/unencrypted password check
+// encrypted/unencrypted password check (from eApp)
//-----------------------------------------------------
bool check_encrypted(const char* str1, const char* str2, const char* passwd)
{
char md5str[64], md5bin[32];
- snprintf(md5str, sizeof(md5str), "%s%s", str1, str2);
- md5str[sizeof(md5str)-1] = '\0';
+ safesnprintf(md5str, sizeof(md5str), "%s%s", str1, str2);
MD5_String2binary(md5str, md5bin);
return (0==memcmp(passwd, md5bin, 16));
}
-bool check_password(struct login_session_data* sd, int passwdenc, const char* passwd, const char* refpass)
+bool check_password(const char* md5key, int passwdenc, const char* passwd, const char* refpass)
{
if(passwdenc == 0)
{
return (0==strcmp(passwd, refpass));
}
- else if(sd != NULL)
+ else
{
- // password mode set to 1 -> (md5key, refpass) enable with <passwordencrypt></passwordencrypt>
- // password mode set to 2 -> (refpass, md5key) enable with <passwordencrypt2></passwordencrypt2>
+ // 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) && check_encrypted(sd->md5key, refpass, passwd)) ||
- ((passwdenc&0x02) && check_encrypted(refpass, sd->md5key, passwd));
+ return ((passwdenc&0x01) && check_encrypted(md5key, refpass, passwd)) ||
+ ((passwdenc&0x02) && check_encrypted(refpass, md5key, passwd));
}
- return false;
}
-//-------------------------------------
-// Make new account
-//-------------------------------------
-int mmo_auth_new(struct mmo_account* account)
+//-----------------------------------------------------
+// custom timestamp formatting (from eApp)
+//-----------------------------------------------------
+const char* timestamp2string(char* str, size_t size, time_t timestamp, const char* format)
{
- static int num_regs = 0; // registration counter
- unsigned int tick = gettick();
-
- time_t expiration_time = 0;
- unsigned int i = auth_num;
-
- // check if the account doesn't exist already
- ARR_FIND( 0, auth_num, i, strcmp(account->userid, auth_dat[i].userid) == 0 );
- if( i < auth_num )
- {
- ShowNotice("Attempt of creation of an already existant account (account: %s_%c, pass: %s, received pass: %s)\n", account->userid, account->sex, auth_dat[i].pass, account->pass);
- return 1; // 1 = Incorrect Password
- }
-
- //Account Registration Flood Protection by [Kevin]
- if( DIFF_TICK(tick, new_reg_tick) < 0 && num_regs >= allowed_regs )
- {
- ShowNotice("Account registration denied (registration limit exceeded)\n");
- return 3;
- }
-
- if (auth_num >= auth_max) {
- auth_max += 256;
- auth_dat = (struct mmo_account*)aRealloc(auth_dat, sizeof(struct mmo_account) * auth_max);
- }
-
- memset(&auth_dat[i], '\0', sizeof(struct mmo_account));
-
- // find a suitable non-gm account id
- while (isGM(account_id_count) > 0)
- account_id_count++;
+ size_t len = strftime(str, size, format, localtime(&timestamp));
+ memset(str + len, '\0', size - len);
+ return str;
+}
- auth_dat[i].account_id = account_id_count++;
- safestrncpy(auth_dat[i].userid, account->userid, NAME_LENGTH);
- if( login_config.use_md5_passwds )
- MD5_String(account->pass, auth_dat[i].pass);
- else
- safestrncpy(auth_dat[i].pass, account->pass, NAME_LENGTH);
- safestrncpy(auth_dat[i].lastlogin, "-", sizeof(auth_dat[i].lastlogin));
- auth_dat[i].sex = account->sex;
- auth_dat[i].logincount = 0;
- auth_dat[i].state = 0;
- safestrncpy(auth_dat[i].email, e_mail_check(account->email) ? account->email : "a@a.com", sizeof(auth_dat[i].email));
- safestrncpy(auth_dat[i].error_message, "-", sizeof(auth_dat[i].error_message));
- auth_dat[i].unban_time = 0;
- if( login_config.start_limited_time != -1 )
- expiration_time = time(NULL) + login_config.start_limited_time;
- auth_dat[i].expiration_time = expiration_time;
- strncpy(auth_dat[i].last_ip, "-", 16);
- strncpy(auth_dat[i].memo, "-", 255);
- auth_dat[i].account_reg2_num = 0;
-
- ShowNotice("Account creation (account %s, id: %d, pass: %s, sex: %c)\n", account->userid, auth_num, account->pass, account->sex);
- auth_num++;
- if( DIFF_TICK(tick, new_reg_tick) > 0 )
- {// Update the registration check.
- num_regs = 0;
- new_reg_tick = tick + time_allowed*1000;
- }
- ++num_regs;
-
- return 0;
+//--------------------------------------------
+// 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;
}
-//-----------------------------------------------------
-// Check/authentication of a connection
-//-----------------------------------------------------
-int mmo_auth(struct login_session_data* sd)
+//----------------------------------
+// Reading Lan Support configuration
+//----------------------------------
+int login_lan_config_read(const char *lancfgName)
{
- unsigned int i;
- time_t raw_time;
- char tmpstr[256];
- size_t len;
- char user_password[32+1]; // reserve for md5-ed pw
-
- char ip[16];
- ip2str(session[sd->fd]->client_addr, ip);
+ FILE *fp;
+ int line_num = 0;
+ char line[1024], w1[64], w2[64], w3[64], w4[64];
- // DNS Blacklist check
- if( login_config.use_dnsbl )
- {
- char r_ip[16];
- char ip_dnsbl[256];
- char* dnsbl_serv;
- bool matched = false;
- uint8* sin_addr = (uint8*)&session[sd->fd]->client_addr;
+ if((fp = fopen(lancfgName, "r")) == NULL) {
+ ShowWarning("LAN Support configuration file is not found: %s\n", lancfgName);
+ return 1;
+ }
- sprintf(r_ip, "%u.%u.%u.%u", sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3]);
+ ShowInfo("Reading the configuration file %s...\n", lancfgName);
- for( dnsbl_serv = strtok(login_config.dnsbl_servs,","); !matched && dnsbl_serv != NULL; dnsbl_serv = strtok(NULL,",") )
- {
- sprintf(ip_dnsbl, "%s.%s", r_ip, dnsbl_serv);
- if( host2ip(ip_dnsbl) )
- matched = true;
- }
+ while(fgets(line, sizeof(line), fp))
+ {
+ line_num++;
+ if ((line[0] == '/' && line[1] == '/') || line[0] == '\n' || line[1] == '\n')
+ continue;
- if( matched )
+ if(sscanf(line,"%[^:]: %[^:]:%[^:]:%[^\r\n]", w1, w2, w3, w4) != 4)
{
- ShowInfo("DNSBL: (%s) Blacklisted. User Kicked.\n", r_ip);
- return 3;
+ ShowWarning("Error syntax of configuration file %s in line %d.\n", lancfgName, line_num);
+ continue;
}
- }
-
- //Client Version check
- if( login_config.check_client_version && sd->version != login_config.client_version_to_connect )
- 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
- account_id_count <= END_ACCOUNT_NUM )
+ if( strcmpi(w1, "subnet") == 0 )
{
- struct mmo_account acc;
- int result;
-
- len -= 2;
- sd->userid[len] = '\0';
-
- memset(&acc, '\0', sizeof(acc));
- safestrncpy(acc.userid, sd->userid, NAME_LENGTH);
- safestrncpy(acc.pass, sd->passwd, NAME_LENGTH);
- safestrncpy(acc.email, "a@a.com", sizeof(acc.email));
- acc.sex = TOUPPER(sd->userid[len+1]);
+ subnet[subnet_count].mask = str2ip(w2);
+ subnet[subnet_count].char_ip = str2ip(w3);
+ subnet[subnet_count].map_ip = str2ip(w4);
- result = mmo_auth_new(&acc);
- if( result )
- return result;// Failed to make account. [Skotlex].
+ 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;
+ }
- auth_before_save_file = 0; // Creation of an account -> save accounts file immediatly
+ subnet_count++;
}
}
-
- // Strict account search
- ARR_FIND( 0, auth_num, i, strcmp(sd->userid, auth_dat[i].userid) == 0 );
- // if strict account search fails, we do a no sensitive case research for index
- if( i < auth_num )
- {
- i = search_account_index(sd->userid);
- if( i == -1 )
- i = auth_num;
- else
- memcpy(sd->userid, auth_dat[i].userid, NAME_LENGTH); // for the possible tests/checks afterwards (copy correcte sensitive case).
- }
+ ShowStatus("Read information about %d subnetworks.\n", subnet_count);
- if( i == auth_num )
- {
- ShowNotice("Unknown account (account: %s, received pass: %s, ip: %s)\n", sd->userid, sd->passwd, ip);
- return 0; // 0 = Unregistered ID
- }
+ fclose(fp);
+ return 0;
+}
- if( login_config.use_md5_passwds )
- MD5_String(sd->passwd, user_password);
- else
- safestrncpy(user_password, sd->passwd, NAME_LENGTH);
+//-----------------------
+// Console Command Parser [Wizputer]
+//-----------------------
+int parse_console(char* buf)
+{
+ char command[256];
- if( !check_password(sd, sd->passwdenc, user_password, auth_dat[i].pass) )
- {
- ShowNotice("Invalid password (account: %s, pass: %s, received pass: %s, ip: %s)\n", sd->userid, auth_dat[i].pass, (sd->passwdenc) ? "[MD5]" : sd->passwd, ip);
- return 1; // 1 = Incorrect Password
- }
+ memset(command, 0, sizeof(command));
- if( auth_dat[i].expiration_time != 0 && auth_dat[i].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
- }
+ sscanf(buf, "%[^\n]", command);
- if( auth_dat[i].unban_time != 0 && auth_dat[i].unban_time > time(NULL) )
- {
- strftime(tmpstr, 20, login_config.date_format, localtime(&auth_dat[i].unban_time));
- tmpstr[19] = '\0';
- 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
- }
+ ShowInfo("Console command :%s", command);
- if( auth_dat[i].state )
- {
- ShowNotice("Connection refused (account: %s, pass: %s, state: %d, ip: %s)\n", sd->userid, sd->passwd, auth_dat[i].state, ip);
- return auth_dat[i].state - 1;
+ if( strcmpi("shutdown", command) == 0 ||
+ strcmpi("exit", command) == 0 ||
+ strcmpi("quit", command) == 0 ||
+ strcmpi("end", 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");
+ else
+ if( strcmpi("help", command) == 0 ) {
+ ShowInfo(CL_BOLD"Help of commands:"CL_RESET"\n");
+ ShowInfo(" To shutdown the server:\n");
+ ShowInfo(" 'shutdown|exit|quit|end'\n");
+ ShowInfo(" To know if server is alive:\n");
+ ShowInfo(" 'alive|status'\n");
}
- ShowNotice("Authentication accepted (account: %s, id: %d, ip: %s)\n", sd->userid, auth_dat[i].account_id, ip);
-
- // auth start : time seed
- time(&raw_time);
- strftime(tmpstr, 24, "%Y-%m-%d %H:%M:%S",localtime(&raw_time));
-
- sd->account_id = auth_dat[i].account_id;
- sd->login_id1 = rand();
- sd->login_id2 = rand();
- safestrncpy(sd->lastlogin, auth_dat[i].lastlogin, 24);
- sd->sex = auth_dat[i].sex;
-
- 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);
-
- safestrncpy(auth_dat[i].lastlogin, tmpstr, sizeof(auth_dat[i].lastlogin));
- safestrncpy(auth_dat[i].last_ip, ip, sizeof(auth_dat[i].last_ip));
- auth_dat[i].unban_time = 0;
- auth_dat[i].logincount++;
-
- // Save until for change ip/time of auth is not very useful => limited save for that
- // Save there informations isnot necessary, because they are saved in log file.
- if (--auth_before_save_file <= 0) // Reduce counter. 0 or less, we save
- mmo_auth_sync();
-
- return -1; // account OK
-}
-
-static int online_db_setoffline(DBKey key, void* data, va_list ap)
-{
- struct online_login_data* p = (struct online_login_data*)data;
- int server = va_arg(ap, int);
- if( server == -1 )
- {
- p->char_server = -1;
- if( p->waiting_disconnect != -1 )
- {
- delete_timer(p->waiting_disconnect, waiting_disconnect_timer);
- p->waiting_disconnect = -1;
- }
- }
- else if( p->char_server == server )
- p->char_server = -2; //Char server disconnected.
return 0;
}
+
//--------------------------------
// Packet parsing for char-servers
//--------------------------------
int parse_fromchar(int fd)
{
- unsigned int i;
int j, id;
uint32 ipl;
char ip[16];
@@ -1117,14 +398,6 @@ int parse_fromchar(int fd)
switch( command )
{
- case 0x2709: // request from map-server via char-server to reload GM accounts
- RFIFOSKIP(fd,2);
- ShowStatus("Char-server '%s': Request to re-load GM configuration file (ip: %s).\n", server[id].name, ip);
- read_gm_account();
- // send GM accounts to all char-servers
- send_GM_accounts(-1);
- break;
-
case 0x2712: // request from char-server to authenticate an account
if( RFIFOREST(fd) < 19 )
return 0;
@@ -1132,8 +405,8 @@ int parse_fromchar(int fd)
struct auth_node* node;
int account_id = RFIFOL(fd,2);
- int login_id1 = RFIFOL(fd,6);
- int login_id2 = RFIFOL(fd,10);
+ uint32 login_id1 = RFIFOL(fd,6);
+ uint32 login_id2 = RFIFOL(fd,10);
char sex = sex_num2str(RFIFOB(fd,14));
uint32 ip_ = ntohl(RFIFOL(fd,15));
RFIFOSKIP(fd,19);
@@ -1146,51 +419,49 @@ int parse_fromchar(int fd)
node->sex == sex &&
node->ip == ip_ )
{// found
- uint32 expiration_time;
- char email[40];
- unsigned int k;
+ struct mmo_account acc;
+ time_t expiration_time = 0;
+ const char* email = "";
+ int gmlevel = 0;
//ShowStatus("Char-server '%s': authentication of the account %d accepted (ip: %s).\n", server[id].name, account_id, ip);
// each auth entry can only be used once
idb_remove(auth_db, account_id);
- // retrieve email and account expiration time
- ARR_FIND( 0, auth_num, k, auth_dat[k].account_id == account_id );
- if( k < auth_num )
- {
- strcpy(email, auth_dat[k].email);
- expiration_time = (uint32)auth_dat[k].expiration_time;
- }
- else
+ // retrieve email and account expiration time and gm level
+ if( accounts->load_num(accounts, &acc, account_id) )
{
- memset(email, 0, sizeof(email));
- expiration_time = 0;
+ email = acc.email;
+ expiration_time = acc.expiration_time;
+ gmlevel = acc.level;
}
// send ack
- WFIFOHEAD(fd,59);
+ WFIFOHEAD(fd,60);
WFIFOW(fd,0) = 0x2713;
WFIFOL(fd,2) = account_id;
WFIFOL(fd,6) = login_id1;
WFIFOL(fd,10) = login_id2;
WFIFOB(fd,14) = 0;
- memcpy(WFIFOP(fd,15), email, 40);
- WFIFOL(fd,55) = expiration_time;
- WFIFOSET(fd,59);
+ safestrncpy((char*)WFIFOP(fd,15), email, 40);
+ WFIFOL(fd,55) = (uint32)expiration_time;
+ WFIFOB(fd,59) = gmlevel;
+ WFIFOSET(fd,60);
}
else
{// authentication not found
ShowStatus("Char-server '%s': authentication of the account %d REFUSED (ip: %s).\n", server[id].name, account_id, ip);
- WFIFOHEAD(fd,59);
+ WFIFOHEAD(fd,60);
WFIFOW(fd,0) = 0x2713;
WFIFOL(fd,2) = account_id;
WFIFOL(fd,6) = login_id1;
WFIFOL(fd,10) = login_id2;
WFIFOB(fd,14) = 1;
- // It is unnecessary to send email
- // It is unnecessary to send validity date of the account
- WFIFOSET(fd,59);
+ //safestrncpy((char*)WFIFOP(fd,15), "", 40);
+ //WFIFOL(fd,55) = (uint32)0;
+ //WFIFOB(fd,59) = 0;
+ WFIFOSET(fd,60);
}
}
break;
@@ -1216,25 +487,23 @@ int parse_fromchar(int fd)
if (RFIFOREST(fd) < 46)
return 0;
{
+ struct mmo_account acc;
char email[40];
- int acc = RFIFOL(fd,2);
+
+ 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", server[id].name, acc, ip);
+ 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", server[id].name, account_id, ip);
else
- {
- ARR_FIND( 0, auth_num, i, auth_dat[i].account_id == acc && (strcmp(auth_dat[i].email, "a@a.com") == 0 || auth_dat[i].email[0] == '\0') );
- if( i == auth_num )
- 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", server[id].name, acc, ip);
- else
- {
- memcpy(auth_dat[i].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", server[id].name, acc, email, ip);
- // Save
- mmo_auth_sync();
- }
+ 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", 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", server[id].name, account_id, email, ip);
+ // Save
+ accounts->save(accounts, &acc);
}
}
break;
@@ -1243,27 +512,30 @@ int parse_fromchar(int fd)
if( RFIFOREST(fd) < 6 )
return 0;
{
- uint32 expiration_time = 0;
+ struct mmo_account acc;
+ time_t expiration_time = 0;
char email[40] = "";
+ int gmlevel = 0;
int account_id = RFIFOL(fd,2);
RFIFOSKIP(fd,6);
- ARR_FIND( 0, auth_num, i, auth_dat[i].account_id == account_id );
- if( i == auth_num )
- ShowNotice("Char-server '%s': e-mail of the account %d NOT found (ip: %s).\n", server[id].name, RFIFOL(fd,2), ip);
+ if( !accounts->load_num(accounts, &acc, account_id) )
+ ShowNotice("Char-server '%s': e-mail of the account %d NOT found (ip: %s).\n", server[id].name, account_id, ip);
else
{
- safestrncpy(email, auth_dat[i].email, sizeof(email));
- expiration_time = (uint32)auth_dat[i].expiration_time;
+ safestrncpy(email, acc.email, sizeof(email));
+ expiration_time = acc.expiration_time;
+ gmlevel = acc.level;
}
- WFIFOHEAD(fd,50);
+ WFIFOHEAD(fd,51);
WFIFOW(fd,0) = 0x2717;
WFIFOL(fd,2) = account_id;
safestrncpy((char*)WFIFOP(fd,6), email, 40);
- WFIFOL(fd,46) = expiration_time;
- WFIFOSET(fd,50);
+ WFIFOL(fd,46) = (uint32)expiration_time;
+ WFIFOB(fd,50) = gmlevel;
+ WFIFOSET(fd,51);
}
break;
@@ -1282,32 +554,34 @@ int parse_fromchar(int fd)
if (RFIFOREST(fd) < 86)
return 0;
{
+ 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); remove_control_chars(actual_email);
- safestrncpy(new_email, (char*)RFIFOP(fd,46), 40); remove_control_chars(new_email);
+ 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", server[id].name, account_id, ip);
- else if( e_mail_check(new_email) == 0 )
+ 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", server[id].name, account_id, ip);
- else if( strcmpi(new_email, "a@a.com") == 0 )
+ 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", 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", 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", server[id].name, account_id, acc.userid, acc.email, actual_email, ip);
else {
- ARR_FIND( 0, auth_num, i, auth_dat[i].account_id == account_id );
- if( i == auth_num )
- 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", server[id].name, account_id, ip);
- else
- if( strcmpi(auth_dat[i].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", server[id].name, account_id, auth_dat[i].userid, auth_dat[i].email, actual_email, ip);
- else {
- safestrncpy(auth_dat[i].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", server[id].name, account_id, auth_dat[i].userid, new_email, ip);
- // Save
- mmo_auth_sync();
- }
+ 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", server[id].name, account_id, acc.userid, new_email, ip);
+ // Save
+ accounts->save(accounts, &acc);
}
}
break;
@@ -1316,18 +590,25 @@ int parse_fromchar(int fd)
if (RFIFOREST(fd) < 10)
return 0;
{
+ struct mmo_account acc;
+
int account_id = RFIFOL(fd,2);
- uint32 state = RFIFOL(fd,6);
+ int state = RFIFOL(fd,6);
RFIFOSKIP(fd,10);
- ARR_FIND( 0, auth_num, i, auth_dat[i].account_id == account_id );
- if( i == auth_num )
+ 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", server[id].name, account_id, state, ip);
else
- if( auth_dat[i].state == state )
+ 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", server[id].name, account_id, state, ip);
else {
ShowNotice("Char-server '%s': Status change (account: %d, new status %d, ip: %s).\n", 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;
@@ -1336,9 +617,6 @@ int parse_fromchar(int fd)
WBUFL(buf,7) = state; // status or final date of a banishment
charif_sendallwos(-1, buf, 11);
}
- auth_dat[i].state = state;
- // Save
- mmo_auth_sync();
}
}
break;
@@ -1347,6 +625,8 @@ int parse_fromchar(int fd)
if (RFIFOREST(fd) < 18)
return 0;
{
+ struct mmo_account acc;
+
int account_id = RFIFOL(fd,2);
int year = (short)RFIFOW(fd,6);
int month = (short)RFIFOW(fd,8);
@@ -1356,24 +636,23 @@ int parse_fromchar(int fd)
int sec = (short)RFIFOW(fd,16);
RFIFOSKIP(fd,18);
- ARR_FIND( 0, auth_num, i, auth_dat[i].account_id == account_id );
- if( i == auth_num )
+ if( !accounts->load_num(accounts, &acc, account_id) )
ShowNotice("Char-server '%s': Error of ban request (account: %d not found, ip: %s).\n", server[id].name, account_id, ip);
else
{
time_t timestamp;
struct tm *tmtime;
- if (auth_dat[i].unban_time == 0 || auth_dat[i].unban_time < time(NULL))
- timestamp = time(NULL);
+ if (acc.unban_time == 0 || acc.unban_time < time(NULL))
+ timestamp = time(NULL); // new ban
else
- timestamp = auth_dat[i].unban_time;
+ timestamp = acc.unban_time; // add to existing ban
tmtime = localtime(&timestamp);
tmtime->tm_year = tmtime->tm_year + year;
- tmtime->tm_mon = tmtime->tm_mon + month;
+ tmtime->tm_mon = tmtime->tm_mon + month;
tmtime->tm_mday = tmtime->tm_mday + mday;
tmtime->tm_hour = tmtime->tm_hour + hour;
- tmtime->tm_min = tmtime->tm_min + min;
- tmtime->tm_sec = tmtime->tm_sec + sec;
+ tmtime->tm_min = tmtime->tm_min + min;
+ tmtime->tm_sec = tmtime->tm_sec + sec;
timestamp = mktime(tmtime);
if (timestamp == -1)
ShowNotice("Char-server '%s': Error of ban request (account: %d, invalid date, ip: %s).\n", server[id].name, account_id, ip);
@@ -1382,18 +661,21 @@ int parse_fromchar(int fd)
ShowNotice("Char-server '%s': Error of ban request (account: %d, new date unbans the account, ip: %s).\n", server[id].name, account_id, ip);
else
{
- unsigned char buf[16];
- char tmpstr[2048];
- strftime(tmpstr, 24, login_config.date_format, localtime(&timestamp));
+ 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", server[id].name, account_id, timestamp, tmpstr, ip);
+
+ acc.unban_time = timestamp;
+
+ // Save
+ accounts->save(accounts, &acc);
+
WBUFW(buf,0) = 0x2731;
- WBUFL(buf,2) = auth_dat[i].account_id;
+ WBUFL(buf,2) = account_id;
WBUFB(buf,6) = 1; // 0: change of status, 1: ban
- WBUFL(buf,7) = (unsigned int)timestamp; // status or final date of a banishment
+ WBUFL(buf,7) = (uint32)timestamp; // status or final date of a banishment
charif_sendallwos(-1, buf, 11);
- auth_dat[i].unban_time = timestamp;
- // Save
- mmo_auth_sync();
}
}
}
@@ -1403,29 +685,32 @@ int parse_fromchar(int fd)
if( RFIFOREST(fd) < 6 )
return 0;
{
+ struct mmo_account acc;
+
int account_id = RFIFOL(fd,2);
RFIFOSKIP(fd,6);
- ARR_FIND( 0, auth_num, i, auth_dat[i].account_id == account_id );
- if( i == auth_num )
+ if( !accounts->load_num(accounts, &acc, account_id) )
ShowNotice("Char-server '%s': Error of sex change (account: %d not found, ip: %s).\n", server[id].name, account_id, ip);
else
- if( auth_dat[i].sex == 'S' )
+ if( acc.sex == 'S' )
ShowNotice("Char-server '%s': Error of sex change - account to change is a Server account (account: %d, ip: %s).\n", server[id].name, account_id, ip);
else
{
unsigned char buf[7];
- char sex = ( auth_dat[i].sex == 'M' ) ? 'F' : 'M'; //Change gender
-
- auth_dat[i].sex = sex;
+ char sex = ( acc.sex == 'M' ) ? 'F' : 'M'; //Change gender
ShowNotice("Char-server '%s': Sex change (account: %d, new sex %c, ip: %s).\n", 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);
- // Save
- mmo_auth_sync();
}
}
break;
@@ -1434,37 +719,38 @@ int parse_fromchar(int fd)
if( RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2) )
return 0;
{
- int acc = RFIFOL(fd,4);
+ struct mmo_account acc;
+
+ int account_id = RFIFOL(fd,4);
- ARR_FIND( 0, auth_num, i, auth_dat[i].account_id == acc );
- if( i == auth_num )
- ShowStatus("Char-server '%s': receiving (from the char-server) of account_reg2 (account: %d not found, ip: %s).\n", server[id].name, acc, ip);
+ 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", 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", server[id].name, acc, ip);
- for(j=0,p=13;j<ACCOUNT_REG2_NUM && p<RFIFOW(fd,2);j++){
- sscanf((char*)RFIFOP(fd,p), "%31c%n",auth_dat[i].account_reg2[j].str,&len);
- auth_dat[i].account_reg2[j].str[len]='\0';
+ ShowNotice("char-server '%s': receiving (from the char-server) of account_reg2 (account: %d, ip: %s).\n", 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",auth_dat[i].account_reg2[j].value,&len);
- auth_dat[i].account_reg2[j].value[len]='\0';
+ 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(auth_dat[i].account_reg2[j].str);
- remove_control_chars(auth_dat[i].account_reg2[j].value);
+ remove_control_chars(acc.account_reg2[j].str);
+ remove_control_chars(acc.account_reg2[j].value);
}
- auth_dat[i].account_reg2_num = j;
+ 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));
-
- // Save
- mmo_auth_sync();
}
-
}
break;
@@ -1472,19 +758,21 @@ int parse_fromchar(int fd)
if( RFIFOREST(fd) < 6 )
return 0;
{
+ struct mmo_account acc;
+
int account_id = RFIFOL(fd,2);
RFIFOSKIP(fd,6);
- ARR_FIND( 0, auth_num, i, auth_dat[i].account_id == account_id );
- if( i == auth_num )
+ if( !accounts->load_num(accounts, &acc, account_id) )
ShowNotice("Char-server '%s': Error of UnBan request (account: %d not found, ip: %s).\n", server[id].name, account_id, ip);
else
- if( auth_dat[i].unban_time == 0 )
+ if( acc.unban_time == 0 )
ShowNotice("Char-server '%s': Error of UnBan request (account: %d, no change for unban date, ip: %s).\n", server[id].name, account_id, ip);
else
{
- auth_dat[i].unban_time = 0;
ShowNotice("Char-server '%s': UnBan request (account: %d, ip: %s).\n", server[id].name, account_id, ip);
+ acc.unban_time = 0;
+ accounts->save(accounts, &acc);
}
}
break;
@@ -1531,39 +819,32 @@ int parse_fromchar(int fd)
if (RFIFOREST(fd) < 10)
return 0;
{
+ struct mmo_account acc;
size_t off;
int account_id = RFIFOL(fd,2);
int char_id = RFIFOL(fd,6);
RFIFOSKIP(fd,10);
- WFIFOHEAD(fd,10000);
+ 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
- ARR_FIND( 0, auth_num, i, auth_dat[i].account_id == account_id );
- if( i == auth_num )
+ off = 13;
+ if( accounts->load_num(accounts, &acc, account_id) )
{
- //Account not found? Send at least empty data, map servers need a reply!
- WFIFOW(fd,2) = 13;
- WFIFOSET(fd,WFIFOW(fd,2));
- break;
- }
-
- for( off = 13, j = 0; j < auth_dat[i].account_reg2_num && off < 9000; j++ )
- {
- if( auth_dat[i].account_reg2[j].str[0] != '\0' )
+ for( j = 0; j < acc.account_reg2_num; j++ )
{
- off += sprintf((char*)WFIFOP(fd,off), "%s", auth_dat[i].account_reg2[j].str)+1; //We add 1 to consider the '\0' in place.
- off += sprintf((char*)WFIFOP(fd,off), "%s", auth_dat[i].account_reg2[j].value)+1;
+ 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;
+ }
}
}
- if( off >= 9000 )
- ShowWarning("Too many account2 registries for AID %d. Some registries were not sent.\n", account_id);
-
WFIFOW(fd,2) = (uint16)off;
WFIFOSET(fd,WFIFOW(fd,2));
}
@@ -1594,14 +875,177 @@ int parse_fromchar(int fd)
return 0;
}
-//--------------------------------------------
-// Test to know if an IP come from LAN or WAN.
-//--------------------------------------------
-int lan_subnetcheck(uint32 ip)
+
+//-------------------------------------
+// Make new account
+//-------------------------------------
+int mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_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;
+ 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 >= allowed_regs )
+ {
+ ShowNotice("Account registration denied (registration limit exceeded)\n");
+ return 3;
+ }
+
+ // 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, "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));
+
+ 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 + time_allowed*1000;
+ }
+ ++num_regs;
+
+ return -1;
+}
+
+//-----------------------------------------------------
+// Check/authentication of a connection
+//-----------------------------------------------------
+int mmo_auth(struct login_session_data* sd)
+{
+ 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;
+ bool matched = false;
+ 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,","); !matched && dnsbl_serv != NULL; dnsbl_serv = strtok(NULL,",") )
+ {
+ sprintf(ip_dnsbl, "%s.%s", r_ip, dnsbl_serv);
+ if( host2ip(ip_dnsbl) )
+ matched = true;
+ }
+
+ if( matched )
+ {
+ 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 )
+ 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 = 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( !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;
+ }
+
+ 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 = rand();
+ sd->login_id2 = rand();
+ safestrncpy(sd->lastlogin, acc.lastlogin, sizeof(sd->lastlogin));
+ sd->sex = acc.sex;
+ sd->level = acc.level;
+
+ // update account data
+ timestamp2string(acc.lastlogin, sizeof(acc.lastlogin), time(NULL), login_config.date_format);
+ 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
}
void login_auth_ok(struct login_session_data* sd)
@@ -1614,8 +1058,6 @@ void login_auth_ok(struct login_session_data* sd)
struct auth_node* node;
int i;
- sd->level = isGM(sd->account_id);
-
if( sd->level < login_config.min_level_to_connect )
{
ShowStatus("Connection refused: the minimum GM level for connection is %d (account: %s, GM level: %d).\n", login_config.min_level_to_connect, sd->userid, sd->level);
@@ -1673,6 +1115,8 @@ void login_auth_ok(struct login_session_data* sd)
}
}
+ login_log(ip, sd->userid, 100, "login ok");
+
if( sd->level > 0 )
ShowStatus("Connection of the GM (level:%d) account '%s' accepted.\n", sd->level, sd->userid);
else
@@ -1728,6 +1172,42 @@ void login_auth_ok(struct login_session_data* sd)
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)
+ {
+ const char* error;
+ switch( result ) {
+ case 0: error = "Unregistered ID."; break; // 0 = Unregistered ID
+ case 1: error = "Incorrect Password."; break; // 1 = Incorrect Password
+ case 2: error = "Account Expired."; break; // 2 = This ID is expired
+ case 3: error = "Rejected from server."; break; // 3 = Rejected from Server
+ case 4: error = "Blocked by GM."; break; // 4 = You have been blocked by the GM Team
+ case 5: error = "Not latest game EXE."; break; // 5 = Your Game's EXE file is not the latest version
+ case 6: error = "Banned."; break; // 6 = Your are Prohibited to log in until %s
+ case 7: error = "Server Over-population."; break; // 7 = Server is jammed due to over populated
+ case 8: error = "Account limit from company"; break; // 8 = No more accounts may be connected from this company
+ case 9: error = "Ban by DBA"; break; // 9 = MSI_REFUSE_BAN_BY_DBA
+ case 10: error = "Email not confirmed"; break; // 10 = MSI_REFUSE_EMAIL_NOT_CONFIRMED
+ case 11: error = "Ban by GM"; break; // 11 = MSI_REFUSE_BAN_BY_GM
+ case 12: error = "Working in DB"; break; // 12 = MSI_REFUSE_TEMP_BAN_FOR_DBWORK
+ case 13: error = "Self Lock"; break; // 13 = MSI_REFUSE_SELF_LOCK
+ case 14: error = "Not Permitted Group"; break; // 14 = MSI_REFUSE_NOT_PERMITTED_GROUP
+ case 15: error = "Not Permitted Group"; break; // 15 = MSI_REFUSE_NOT_PERMITTED_GROUP
+ case 99: error = "Account gone."; break; // 99 = This ID has been totally erased
+ case 100: error = "Login info remains."; break; // 100 = Login information remains at %s
+ case 101: error = "Hacking investigation."; break; // 101 = Account has been locked for a hacking investigation. Please contact the GM Team for more information
+ case 102: error = "Bug investigation."; break; // 102 = This account has been temporarily prohibited from login due to a bug-related investigation
+ case 103: error = "Deleting char."; break; // 103 = This character is being deleted. Login is temporarily unavailable for the time being
+ case 104: error = "Deleting spouse char."; break; // 104 = This character is being deleted. Login is temporarily unavailable for the time being
+ default : error = "Unknown Error."; break;
+ }
+
+ login_log(ip, sd->userid, result, error);
+ }
+
+ if( result == 1 && login_config.dynamic_pass_failure_ban )
+ ipban_log(ip); // log failed password attempt
WFIFOHEAD(fd,23);
WFIFOW(fd,0) = 0x6a;
@@ -1736,39 +1216,54 @@ void login_auth_failed(struct login_session_data* sd, int result)
memset(WFIFOP(fd,3), '\0', 20);
else
{// 6 = Your are Prohibited to log in until %s
- char tmpstr[20];
- int i = search_account_index(sd->userid);
- time_t unban_time = ( i >= 0 ) ? auth_dat[i].unban_time : 0;
- strftime(tmpstr, 20, login_config.date_format, localtime(&unban_time));
- safestrncpy((char*)WFIFOP(fd,3), tmpstr, 20); // ban timestamp goes here
+ 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);
}
+
//----------------------------------------------------------------------------------------
// Default packet parsing (normal players or administation/char-server connection requests)
//----------------------------------------------------------------------------------------
int parse_login(int fd)
{
- struct login_session_data* sd = session[fd]->session_data;
+ struct login_session_data* sd = (struct login_session_data*)session[fd]->session_data;
int result;
- uint32 ipl;
+
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 ) {
- sd = CREATE(session[fd]->session_data, struct login_session_data, 1);
+ 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;
}
- ipl = session[fd]->client_addr;
- ip2str(ipl, ip);
-
while( RFIFOREST(fd) >= 2 )
{
uint16 command = RFIFOW(fd,0);
@@ -1782,42 +1277,55 @@ int parse_login(int fd)
RFIFOSKIP(fd,26);
break;
- case 0x0204: // New alive packet: structure: 0x204 <encrypted.account.userid>.16B. (new ragexe from 22 june 2004)
+ // client md5 hash (binary)
+ case 0x0204: // S 0204 <md5 hash>.16B (kRO 2004-05-31aSakexe langtype 0 and 6)
if (RFIFOREST(fd) < 18)
return 0;
RFIFOSKIP(fd,18);
break;
- case 0x0064: // request client login
- case 0x01dd: // request client login (encryption mode)
- case 0x0277: // New login packet (kRO 2006-04-24aSakexe langtype 0)
- case 0x02b0: // New login packet (kRO 2007-05-14aSakexe langtype 0)
+ // request client login
+ case 0x0064: // S 0064 <version>.l <username>.24B <password>.24B <version2>.B
+ case 0x01dd: // S 01dd <version>.l <username>.24B <md5 hash>.16B <version2>.B
+ case 0x0277: // S 0277 <version>.l <username>.24B <password>.24B <junk?>.29B <version2>.B (kRO 2006-04-24aSakexe langtype 0)
+ case 0x02b0: // S 02b0 <version>.l <username>.24B <password>.24B <???>.B <ip address>.16S <mac address>.13S <version2>.B (kRO 2007-05-14aSakexe langtype 0)
{
- size_t packet_len = RFIFOREST(fd); // assume no other packet was sent
+ size_t packet_len = RFIFOREST(fd);
if( (command == 0x0064 && packet_len < 55)
|| (command == 0x01dd && packet_len < 47)
|| (command == 0x0277 && packet_len < 84)
|| (command == 0x02b0 && packet_len < 85) )
- return 0;
-
- // S 0064 <version>.l <account name>.24B <password>.24B <version2>.B
- // S 01dd <version>.l <account name>.24B <md5 binary>.16B <version2>.B
- // S 0277 <version>.l <account name>.24B <password>.24B <junk?>.29B <version2>.B
- // S 02b0 <version>.l <account name>.24B <password>.24B <junk?>.30B <version2>.B
-
- sd->version = RFIFOL(fd,2);
- safestrncpy(sd->userid, (char*)RFIFOP(fd,6), NAME_LENGTH); remove_control_chars(sd->userid);
- if (command != 0x01dd) {
+ return 0;
+ }
+ {
+ int version = RFIFOL(fd,2);
+ char* userid = (char*)RFIFOP(fd,6);
+ char* passwd = (char*)RFIFOP(fd,30);
+ RFIFOSKIP(fd,RFIFOREST(fd)); // assume no other packet was sent
+
+ sd->version = version;
+ safestrncpy(sd->userid, userid, NAME_LENGTH);
+ if( command != 0x01dd )
+ {
ShowStatus("Request for connection of %s (ip: %s).\n", sd->userid, ip);
- safestrncpy(sd->passwd, (char*)RFIFOP(fd,30), NAME_LENGTH); remove_control_chars(sd->passwd);
+ safestrncpy(sd->passwd, passwd, NAME_LENGTH);
+ if( login_config.use_md5_passwds )
+ MD5_String(sd->passwd, sd->passwd);
sd->passwdenc = 0;
- } else {
+ }
+ else
+ {
ShowStatus("Request for connection (encryption mode) of %s (ip: %s).\n", sd->userid, ip);
- memcpy(sd->passwd, RFIFOP(fd,30), 16); sd->passwd[16] = '\0'; // raw binary data here!
+ memcpy(sd->passwd, passwd, 16); sd->passwd[16] = '\0'; // raw binary data here!
sd->passwdenc = PASSWORDENC;
}
- RFIFOSKIP(fd,packet_len);
+
+ if( sd->passwdenc != 0 && login_config.use_md5_passwds )
+ {
+ login_auth_failed(sd, 3); // send "rejected from server"
+ return 0;
+ }
result = mmo_auth(sd);
@@ -1852,25 +1360,26 @@ int parse_login(int fd)
return 0;
{
char server_name[20];
+ char message[256];
uint32 server_ip;
uint16 server_port;
uint16 maintenance;
uint16 new_;
- safestrncpy(sd->userid, (char*)RFIFOP(fd,2), NAME_LENGTH); //remove_control_chars(account.userid);
- safestrncpy(sd->passwd, (char*)RFIFOP(fd,26), NAME_LENGTH); //remove_control_chars(account.passwd);
+ safestrncpy(sd->userid, (char*)RFIFOP(fd,2), NAME_LENGTH);
+ safestrncpy(sd->passwd, (char*)RFIFOP(fd,26), NAME_LENGTH);
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); remove_control_chars(server_name);
+ safestrncpy(server_name, (char*)RFIFOP(fd,60), 20);
maintenance = RFIFOW(fd,82);
new_ = RFIFOW(fd,84);
RFIFOSKIP(fd,86);
- ShowInfo("Connection request of the char-server '%s' @ %d.%d.%d.%d:%d (account: '%s', pass: '%s', ip: '%s')\n", server_name, CONVIP(server_ip), server_port, sd->userid, sd->passwd, ip);
+ 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);
if( result == -1 && sd->sex == 'S' && sd->account_id < MAX_SERVERS && server[sd->account_id].fd == -1 )
@@ -1893,9 +1402,6 @@ int parse_login(int fd)
WFIFOW(fd,0) = 0x2711;
WFIFOB(fd,2) = 0;
WFIFOSET(fd,3);
-
- // send GM account to char-server
- send_GM_accounts(fd);
}
else
{
@@ -1926,57 +1432,25 @@ int parse_login(int fd)
case 0x7918: // Request for administation login
if ((int)RFIFOREST(fd) < 4 || (int)RFIFOREST(fd) < ((RFIFOW(fd,2) == 0) ? 28 : 20))
return 0;
- WFIFOW(fd,0) = 0x7919;
- WFIFOB(fd,2) = 1;
- if( session[fd]->client_addr != admin_allowed_ip ) {
- ShowNotice("'ladmin'-login: Connection in administration mode refused: IP isn't authorised (ladmin_allow, ip: %s).\n", ip);
- } else {
- struct login_session_data *ld = (struct login_session_data*)session[fd]->session_data;
- if (RFIFOW(fd,2) == 0) { // non encrypted password
- char password[25];
- memcpy(password, RFIFOP(fd,4), 24);
- password[24] = '\0';
- remove_control_chars(password);
- if( !admin_state )
- ShowNotice("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (non encrypted password: %s, ip: %s)\n", password, ip);
- else
- if( strcmp(password, admin_pass) != 0)
- ShowNotice("'ladmin'-login: Connection in administration mode REFUSED - invalid password (non encrypted password: %s, ip: %s)\n", password, ip);
- else {
- // If remote administration is enabled and password sent by client matches password read from login server configuration file
- ShowNotice("'ladmin'-login: Connection in administration mode accepted (non encrypted password: %s, ip: %s)\n", password, ip);
- WFIFOB(fd,2) = 0;
- session[fd]->func_parse = parse_admin;
- }
- } else { // encrypted password
- if (!ld)
- ShowError("'ladmin'-login: error! MD5 key not created/requested for an administration login.\n");
- else {
- char md5str[64] = "", md5bin[32];
- if (RFIFOW(fd,2) == 1) {
- sprintf(md5str, "%s%s", ld->md5key, admin_pass); // 20 24
- } else if (RFIFOW(fd,2) == 2) {
- sprintf(md5str, "%s%s", admin_pass, ld->md5key); // 24 20
- }
- MD5_String2binary(md5str, md5bin);
- if( !admin_state )
- ShowNotice("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (encrypted password, ip: %s)\n", ip);
- else
- if( memcmp(md5bin, RFIFOP(fd,4), 16) != 0 )
- ShowNotice("'ladmin'-login: Connection in administration mode REFUSED - invalid password (encrypted password, ip: %s)\n", ip);
- else {
- // If remote administration is enabled and password hash sent by client matches hash of password read from login server configuration file
- ShowNotice("'ladmin'-login: Connection in administration mode accepted (encrypted password, ip: %s)\n", ip);
- ShowNotice("Connection of a remote administration accepted (encrypted password).\n");
- WFIFOB(fd,2) = 0;
- session[fd]->func_parse = parse_admin;
- }
- }
- }
+ {
+ int passwdenc = (int)RFIFOW(fd,2);
+ const char* passwd = (char*)RFIFOP(fd,4);
+
+ if( passwdenc == 0 ) { // non encrypted password
+ safestrncpy(sd->passwd, passwd, NAME_LENGTH);
+ sd->passwdenc = 0;
+ } else { // encrypted password
+ memcpy(sd->passwd, passwd, 16); sd->passwd[16] = '\0'; // raw binary data here!
+ sd->passwdenc = passwdenc;
}
- WFIFOSET(fd,3);
- RFIFOSKIP(fd, (RFIFOW(fd,2) == 0) ? 28 : 20);
+ RFIFOSKIP(fd, (passwdenc == 0) ? 28 : 20);
+
+ WFIFOHEAD(fd,3);
+ WFIFOW(fd,0) = 0x7919;
+ WFIFOB(fd,2) = ladmin_auth(sd, ip) ? 0 : 1;
+ WFIFOSET(fd,3);
+ }
break;
default:
@@ -1990,102 +1464,30 @@ int parse_login(int fd)
return 0;
}
-//-----------------------
-// Console Command Parser [Wizputer]
-//-----------------------
-int parse_console(char* buf)
-{
- char command[256];
-
- memset(command, 0, sizeof(command));
- sscanf(buf, "%[^\n]", command);
-
- ShowInfo("Console command :%s", command);
-
- if( strcmpi("shutdown", command) == 0 ||
- strcmpi("exit", command) == 0 ||
- strcmpi("quit", command) == 0 ||
- strcmpi("end", 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");
- else
- if( strcmpi("help", command) == 0 ) {
- ShowInfo(CL_BOLD"Help of commands:"CL_RESET"\n");
- ShowInfo(" To shutdown the server:\n");
- ShowInfo(" 'shutdown|exit|quit|end'\n");
- ShowInfo(" To know if server is alive:\n");
- ShowInfo(" 'alive|status'\n");
- }
-
- return 0;
-}
-
-static int online_data_cleanup_sub(DBKey key, void *data, va_list ap)
-{
- struct online_login_data *character= (struct online_login_data*)data;
- if (character->char_server == -2) //Unknown server.. set them offline
- remove_online_user(character->account_id);
- return 0;
-}
-
-static int online_data_cleanup(int tid, unsigned int tick, int id, intptr data)
-{
- online_db->foreach(online_db, online_data_cleanup_sub);
- return 0;
-}
-
-//----------------------------------
-// Reading Lan Support configuration
-//----------------------------------
-int login_lan_config_read(const char *lancfgName)
+void login_set_defaults()
{
- 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;
- }
-
- ShowInfo("Reading the configuration file %s...\n", lancfgName);
-
- while(fgets(line, sizeof(line), fp))
- {
- line_num++;
- if ((line[0] == '/' && line[1] == '/') || line[0] == '\n' || line[1] == '\n')
- continue;
-
- if(sscanf(line,"%[^:]: %[^:]:%[^:]:%[^\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++;
- }
- }
-
- ShowStatus("Read information about %d subnetworks.\n", subnet_count);
+ login_config.login_ip = INADDR_ANY;
+ login_config.login_port = 6900;
+ 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.use_md5_passwds = false;
+ login_config.min_level_to_connect = 0;
+ login_config.online_check = true;
+ login_config.check_client_version = false;
+ login_config.client_version_to_connect = 20;
- fclose(fp);
- return 0;
+ 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));
}
//-----------------------------------
@@ -2129,26 +1531,6 @@ int login_config_read(const char* cfgName)
else if(!strcmpi(w1, "log_login"))
login_config.log_login = (bool)config_switch(w2);
- else if (strcmpi(w1, "admin_state") == 0) {
- admin_state = (bool)config_switch(w2);
- } else if (strcmpi(w1, "admin_pass") == 0) {
- memset(admin_pass, 0, sizeof(admin_pass));
- strncpy(admin_pass, w2, sizeof(admin_pass));
- admin_pass[sizeof(admin_pass)-1] = '\0';
- } else if (strcmpi(w1, "admin_allowed_ip") == 0)
- admin_allowed_ip = host2ip(w2);
- else if (strcmpi(w1, "account_filename") == 0) {
- memset(account_filename, 0, sizeof(account_filename));
- strncpy(account_filename, w2, sizeof(account_filename));
- account_filename[sizeof(account_filename)-1] = '\0';
- } else if (strcmpi(w1, "gm_account_filename") == 0) {
- memset(GM_account_filename, 0, sizeof(GM_account_filename));
- strncpy(GM_account_filename, w2, sizeof(GM_account_filename));
- GM_account_filename[sizeof(GM_account_filename)-1] = '\0';
- }
- else if (strcmpi(w1, "gm_account_filename_check_timer") == 0)
- gm_account_filename_check_timer = atoi(w2);
-
else if(!strcmpi(w1, "new_account"))
login_config.new_account_flag = (bool)config_switch(w2);
else if(!strcmpi(w1, "start_limited_time"))
@@ -2165,8 +1547,6 @@ int login_config_read(const char* cfgName)
safestrncpy(login_config.date_format, w2, sizeof(login_config.date_format));
else if(!strcmpi(w1, "console"))
login_config.console = (bool)config_switch(w2);
-// else if(!strcmpi(w1, "case_sensitive"))
-// login_config.case_sensitive = config_switch(w2);
else if(!strcmpi(w1, "allowed_regs")) //account flood protection system
allowed_regs = atoi(w2);
else if(!strcmpi(w1, "time_allowed"))
@@ -2179,77 +1559,87 @@ int login_config_read(const char* cfgName)
safestrncpy(login_config.dnsbl_servs, w2, sizeof(login_config.dnsbl_servs));
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, "admin_state"))
+ login_config.admin_state = (bool)config_switch(w2);
+ else if(!strcmpi(w1, "admin_pass"))
+ safestrncpy(login_config.admin_pass, w2, sizeof(login_config.admin_pass));
+ else if(!strcmpi(w1, "admin_allowed_host"))
+ safestrncpy(login_config.admin_allowed_host, w2, sizeof(login_config.admin_pass));
+
else if(!strcmpi(w1, "import"))
login_config_read(w2);
+ else
+ if( ipban_config_read(w1, w2) )
+ continue;
+ else if(!strcmpi(w1, "account.engine"))
+ safestrncpy(login_config.account_engine, w2, sizeof(login_config.account_engine));
+ else
+ {// try the account engines
+ int i;
+ for( i = 0; account_engines[i].constructor; ++i )
+ {
+ AccountDB* db = account_engines[i].db;
+ if( db && db->set_property(db, w1, w2) )
+ break;
+ }
+ }
}
fclose(fp);
ShowInfo("Finished reading %s.\n", cfgName);
return 0;
}
-//-------------------------------------
-// Displaying of configuration warnings
-//-------------------------------------
-void display_conf_warnings(void)
+//-----------------------------------
+// Reading interserver configuration file
+//-----------------------------------
+void inter_config_read(const char* cfgName)
{
- if( admin_state ) {
- if (admin_pass[0] == '\0') {
- ShowWarning("Administrator password is void (admin_pass).\n");
- } else if (strcmp(admin_pass, "admin") == 0) {
- ShowWarning("You are using the default administrator password (admin_pass).\n");
- ShowWarning(" We highly recommend that you change it.\n");
- }
- }
-
- if (gm_account_filename_check_timer < 0) {
- ShowWarning("Invalid value for gm_account_filename_check_timer parameter. Setting to 15 sec (default).\n");
- gm_account_filename_check_timer = 15;
- } else if (gm_account_filename_check_timer == 1) {
- ShowWarning("Invalid value for gm_account_filename_check_timer parameter. Setting to 2 sec (minimum value).\n");
- gm_account_filename_check_timer = 2;
+ char line[1024], w1[1024], w2[1024];
+ FILE* fp = fopen(cfgName, "r");
+ if(fp == NULL) {
+ ShowError("file not found: %s\n", cfgName);
+ return;
}
+ ShowInfo("reading configuration file %s...\n", cfgName);
+ while(fgets(line, sizeof(line), fp))
+ {
+ if (line[0] == '/' && line[1] == '/')
+ continue;
+ if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) < 2)
+ continue;
- if (login_config.min_level_to_connect < 0) { // 0: all players, 1-99 at least gm level x
- ShowWarning("Invalid value for min_level_to_connect (%d) parameter -> setting 0 (any player).\n", login_config.min_level_to_connect);
- login_config.min_level_to_connect = 0;
- } else if (login_config.min_level_to_connect > 99) { // 0: all players, 1-99 at least gm level x
- ShowWarning("Invalid value for min_level_to_connect (%d) parameter -> setting to 99 (only GM level 99)\n", login_config.min_level_to_connect);
- login_config.min_level_to_connect = 99;
- }
+ // settings common for multiple components
+ ipban_config_read(w1,w2);
+ loginlog_config_read(w1,w2);
- if (login_config.start_limited_time < -1) { // -1: create unlimited account, 0 or more: additionnal sec from now to create limited time
- ShowWarning("Invalid value for start_limited_time parameter\n");
- ShowWarning(" -> setting to -1 (new accounts are created with unlimited time).\n");
- login_config.start_limited_time = -1;
+ if (!strcmpi(w1, "import"))
+ inter_config_read(w2);
}
-
- return;
+ fclose(fp);
+ ShowInfo("Done reading %s.\n", cfgName);
}
-void login_set_defaults()
+/// Get the engine selected in the config settings.
+/// Updates the config setting with the selected engine if 'auto'.
+static AccountDB* get_account_engine(void)
{
- login_config.login_ip = INADDR_ANY;
- login_config.login_port = 6900;
- 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.case_sensitive = true;
- login_config.use_md5_passwds = false;
-// login_config.login_gm_read = true;
- login_config.min_level_to_connect = 0;
- login_config.online_check = true;
- login_config.check_client_version = false;
- login_config.client_version_to_connect = 20;
+ int i;
+ bool get_first = (strcmp(login_config.account_engine,"auto") == 0);
-// 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));
+ 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;
}
//--------------------------------------
@@ -2258,15 +1648,28 @@ void login_set_defaults()
void do_final(void)
{
int i, fd;
+
+ login_log(0, "login server", 100, "login server shutdown");
ShowStatus("Terminating...\n");
- mmo_auth_sync();
+ if( login_config.log_login )
+ loginlog_final();
+
+ ipban_final();
+
+ for( i = 0; account_engines[i].constructor; ++i )
+ {// destroy all account engines
+ AccountDB* db = account_engines[i].db;
+ if( db )
+ {
+ db->destroy(db);
+ account_engines[i].db = NULL;
+ }
+ }
+ accounts = NULL; // destroyed in account_engines
online_db->destroy(online_db, NULL);
auth_db->destroy(auth_db, NULL);
- if(auth_dat) aFree(auth_dat);
- if(gm_account_db) aFree(gm_account_db);
-
for (i = 0; i < MAX_SERVERS; i++) {
if ((fd = server[i].fd) >= 0) {
memset(&server[i], 0, sizeof(struct mmo_char_server));
@@ -2299,20 +1702,27 @@ int do_init(int argc, char** argv)
{
int i;
- login_set_defaults();
+ // intialize engines (to accept config settings)
+ for( i = 0; account_engines[i].constructor; ++i )
+ account_engines[i].db = account_engines[i].constructor();
// read login-server configuration
+ login_set_defaults();
login_config_read((argc > 1) ? argv[1] : LOGIN_CONF_NAME);
- display_conf_warnings(); // not in login_config_read, because we can use 'import' option, and display same message twice or more
login_lan_config_read((argc > 2) ? argv[2] : LAN_CONF_NAME);
+ inter_config_read(INTER_CONF_NAME);
srand((unsigned int)time(NULL));
for( i = 0; i < MAX_SERVERS; i++ )
server[i].fd = -1;
- // Accounts database init
- mmo_auth_init();
+ // 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);
@@ -2321,21 +1731,9 @@ int do_init(int argc, char** argv)
// Interserver auth init
auth_db = idb_alloc(DB_OPT_RELEASE_DATA);
- // Read account information.
- read_gm_account();
-
// set default parser as parse_login function
set_defaultparse(parse_login);
- add_timer_func_list(check_auth_sync, "check_auth_sync");
- add_timer_interval(gettick() + 60000, check_auth_sync, 0, 0, 60000); // every 60 sec we check if we must save accounts file (only if necessary to save)
-
- // every x sec we check if gm file has been changed
- if( gm_account_filename_check_timer ) {
- add_timer_func_list(check_GM_file, "check_GM_file");
- add_timer_interval(gettick() + gm_account_filename_check_timer * 1000, check_GM_file, 0, 0, gm_account_filename_check_timer * 1000);
- }
-
// 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);
@@ -2346,17 +1744,30 @@ int do_init(int argc, char** argv)
add_timer_interval(gettick() + login_config.ip_sync_interval, sync_ip_addresses, 0, 0, login_config.ip_sync_interval);
}
+ // Account database init
+ accounts = get_account_engine();
+ if( accounts == NULL )
+ {
+ ShowError("do_init: account engine '%s' not found.\n", login_config.account_engine);
+ runflag = 0;
+ return 1;
+ }
+ else
+ {
+ ShowInfo("Using account engine '%s'.\n", login_config.account_engine);
+ accounts->init(accounts);
+ }
+
if( login_config.console )
{
//##TODO invoke a CONSOLE_START plugin event
}
- new_reg_tick = gettick();
-
// server port open & binding
login_fd = make_listen_bind(login_config.login_ip, login_config.login_port);
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;
}
diff --git a/src/login/login.h b/src/login/login.h
index 1147d6bb6..8ffa06eec 100644
--- a/src/login/login.h
+++ b/src/login/login.h
@@ -7,6 +7,7 @@
#include "../common/mmo.h" // NAME_LENGTH
#define LOGIN_CONF_NAME "conf/login_athena.conf"
+#define INTER_CONF_NAME "conf/inter_athena.conf"
#define LAN_CONF_NAME "conf/subnet_athena.conf"
// supported encryption types: 1- passwordencrypt, 2- passwordencrypt2, 3- both
@@ -38,9 +39,9 @@ struct mmo_char_server {
int fd;
uint32 ip;
uint16 port;
- uint16 users; // user count on this server
- uint16 maintenance; // in maintenance mode?
- uint16 new_; // allows creating new chars?
+ uint16 users; // user count on this server
+ uint16 maintenance; // in maintenance mode?
+ uint16 new_; // should display as 'new'?
};
struct Login_Config {
@@ -53,42 +54,28 @@ struct Login_Config {
bool console; // console input system enabled?
bool new_account_flag; // autoregistration via _M/_F ?
int start_limited_time; // new account expiration time (-1: unlimited)
-// bool case_sensitive; // are logins case sensitive ?
bool use_md5_passwds; // work with password hashes instead of plaintext passwords?
-// bool login_gm_read; // should the login server handle info about gm accounts?
int min_level_to_connect; // minimum level of player/GM (0: player, 1-99: GM) to connect
bool online_check; // reject incoming players that are already registered as online ?
bool check_client_version; // check the clientversion set in the clientinfo ?
int 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 attemps ?
-// unsigned int dynamic_pass_failure_ban_interval; // how far to scan the loginlog for password failures
-// 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
+ bool admin_state; // is ladmin support enabled?
+ char admin_pass[24]; // security password for ladmin
+ char admin_allowed_host[32]; // host/ip that is allowed to connect as ladmin
+
+ bool ipban; // perform IP blocking (via contents of `ipbanlist`) ?
+ bool dynamic_pass_failure_ban; // automatic IP blocking due to failed login attemps ?
+ unsigned int dynamic_pass_failure_ban_interval; // how far to scan the loginlog for password failures
+ 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
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)
};
-struct mmo_account {
-
- int account_id;
- char sex;
- char userid[24];
- char pass[32+1]; // 23+1 for normal, 32+1 for md5-ed passwords
- char lastlogin[24];
- int logincount;
- uint32 state; // packet 0x006a value + 1 (0: compte OK)
- char email[40]; // e-mail (by default: a@a.com)
- char error_message[20]; // Message of error code #6 = Your are Prohibited to log in until %s (packet 0x006a)
- time_t unban_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban)
- time_t expiration_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited)
- char last_ip[16]; // save of last IP of connection
- char memo[255]; // a memo field
- int account_reg2_num;
- struct global_reg account_reg2[ACCOUNT_REG2_NUM]; // account script variables (stored on login server)
-};
+extern struct Login_Config login_config;
#endif /* _LOGIN_H_ */
diff --git a/src/login/loginlog.h b/src/login/loginlog.h
new file mode 100644
index 000000000..749d0fbf1
--- /dev/null
+++ b/src/login/loginlog.h
@@ -0,0 +1,14 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#ifndef __LOGINLOG_H_INCLUDED__
+#define __LOGINLOG_H_INCLUDED__
+
+
+void login_log(uint32 ip, const char* username, int rcode, const char* message);
+bool loginlog_init(void);
+bool loginlog_final(void);
+bool loginlog_config_read(const char* w1, const char* w2);
+
+
+#endif // __LOGINLOG_H_INCLUDED__
diff --git a/src/login/loginlog_sql.c b/src/login/loginlog_sql.c
new file mode 100644
index 000000000..96bff6957
--- /dev/null
+++ b/src/login/loginlog_sql.c
@@ -0,0 +1,93 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/cbasetypes.h"
+#include "../common/mmo.h"
+#include "../common/socket.h"
+#include "../common/sql.h"
+#include "../common/strlib.h"
+#include <string.h>
+#include <stdlib.h> // exit
+
+char log_db_ip[32] = "127.0.0.1";
+uint16 log_db_port = 3306;
+char log_db_id[32] = "ragnarok";
+char log_db_pw[32] = "ragnarok";
+char log_db_db[32] = "ragnarok";
+char log_db[32] = "log";
+
+char loginlog_db[256] = "loginlog";
+Sql* sql_handle;
+bool enabled = false;
+
+
+/*=============================================
+ * Records an event in the login log
+ *---------------------------------------------*/
+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;
+
+ if( !enabled )
+ return;
+
+ Sql_EscapeStringLen(sql_handle, esc_username, username, strnlen(username, NAME_LENGTH));
+ Sql_EscapeStringLen(sql_handle, esc_message, message, strnlen(message, 255));
+
+ retcode = Sql_Query(sql_handle,
+ "INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%s', '%s', '%d', '%s')",
+ loginlog_db, ip2str(ip,NULL), esc_username, rcode, message);
+
+ if( retcode != SQL_SUCCESS )
+ Sql_ShowDebug(sql_handle);
+}
+
+bool loginlog_init(void)
+{
+ sql_handle = Sql_Malloc();
+
+ if( SQL_ERROR == Sql_Connect(sql_handle, log_db_id, log_db_pw, log_db_ip, log_db_port, log_db_db) )
+ {
+ Sql_ShowDebug(sql_handle);
+ Sql_Free(sql_handle);
+ exit(EXIT_FAILURE);
+ }
+
+ 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)
+{
+ if( strcmpi(key, "log_db_ip") == 0 )
+ safestrncpy(log_db_ip, value, sizeof(log_db_ip));
+ else
+ if( strcmpi(key, "log_db_port") == 0 )
+ log_db_port = (uint16)strtoul(value, NULL, 10);
+ else
+ if( strcmpi(key, "log_db_id") == 0 )
+ safestrncpy(log_db_id, value, sizeof(log_db_id));
+ else
+ if( strcmpi(key, "log_db_pw") == 0 )
+ safestrncpy(log_db_pw, value, sizeof(log_db_pw));
+ else
+ if( strcmpi(key, "log_db") == 0 )
+ safestrncpy(log_db, value, sizeof(log_db));
+ else
+ if( strcmpi(key, "loginlog_db") == 0 )
+ safestrncpy(loginlog_db, value, sizeof(loginlog_db));
+ else
+ return false;
+
+ return true;
+}
diff --git a/src/login/loginlog_txt.c b/src/login/loginlog_txt.c
new file mode 100644
index 000000000..d40142110
--- /dev/null
+++ b/src/login/loginlog_txt.c
@@ -0,0 +1,74 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/cbasetypes.h"
+#include "../common/mmo.h"
+#include "../common/core.h"
+#include "../common/malloc.h"
+#include "../common/socket.h"
+#include "../common/strlib.h"
+#include "../common/showmsg.h"
+#include "account.h"
+#include "login.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+char login_log_filename[1024] = "log/login.log";
+
+
+
+/*=============================================
+ * Records an event in the login log
+ *---------------------------------------------*/
+void login_log(uint32 ip, const char* username, int rcode, const char* message)
+{
+ FILE* log_fp;
+
+ if( !login_config.log_login )
+ return;
+
+ log_fp = fopen(login_log_filename, "a");
+ if( log_fp != NULL )
+ {
+ char esc_username[NAME_LENGTH*4+1];
+ char esc_message[255*4+1];
+ time_t raw_time;
+ char str_time[24];
+
+ sv_escape_c(esc_username, username, NAME_LENGTH, NULL);
+ sv_escape_c(esc_message, message, 255, NULL);
+
+ time(&raw_time);
+ strftime(str_time, 24, login_config.date_format, localtime(&raw_time));
+ str_time[23] = '\0';
+
+ fprintf(log_fp, "%s\t%s\t%s\t%d\t%s\n", str_time, ip2str(ip,NULL), esc_username, rcode, esc_message);
+
+ fclose(log_fp);
+ }
+}
+
+
+bool loginlog_config_read(const char* w1, const char* w2)
+{
+ if(!strcmpi(w1, "login_log_filename"))
+ safestrncpy(login_log_filename, w2, sizeof(login_log_filename));
+ else
+ return false;
+
+ return true;
+}
+
+
+bool loginlog_init(void)
+{
+ return true;
+}
+
+
+bool loginlog_final(void)
+{
+ return true;
+}