diff options
Diffstat (limited to 'src/login/account.c')
-rw-r--r-- | src/login/account.c | 871 |
1 files changed, 871 insertions, 0 deletions
diff --git a/src/login/account.c b/src/login/account.c new file mode 100644 index 000000000..66ede6cfa --- /dev/null +++ b/src/login/account.c @@ -0,0 +1,871 @@ +/** + * This file is part of Hercules. + * http://herc.ws - http://github.com/HerculesWS/Hercules + * + * Copyright (C) 2012-2016 Hercules Dev Team + * Copyright (C) Athena Dev Teams + * + * Hercules is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#define HERCULES_CORE + +#include "config/core.h" // CONSOLE_INPUT +#include "account.h" + +#include "common/cbasetypes.h" +#include "common/conf.h" +#include "common/console.h" +#include "common/memmgr.h" +#include "common/mmo.h" +#include "common/nullpo.h" +#include "common/showmsg.h" +#include "common/socket.h" +#include "common/sql.h" +#include "common/strlib.h" + +#include <stdlib.h> + +/// global defines +#define ACCOUNT_SQL_DB_VERSION 20110114 + +/// internal structure +typedef struct AccountDB_SQL +{ + AccountDB vtable; // public interface + + struct Sql *accounts; // SQL accounts storage + + // Sql settings + char db_hostname[32]; + uint16 db_port; + char db_username[32]; + char db_password[100]; + char db_database[32]; + char codepage[32]; + // other settings + bool case_sensitive; + char account_db[32]; + char global_acc_reg_num_db[32]; + char global_acc_reg_str_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, struct config_t *config, bool imported); +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; + // Sql settings + 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)); + // other settings + db->case_sensitive = false; + safestrncpy(db->account_db, "login", sizeof(db->account_db)); + safestrncpy(db->global_acc_reg_num_db, "global_acc_reg_num_db", sizeof(db->global_acc_reg_num_db)); + safestrncpy(db->global_acc_reg_str_db, "global_acc_reg_str_db", sizeof(db->global_acc_reg_str_db)); + + return &db->vtable; +} + + +/* ------------------------------------------------------------------------- */ + + +/// establishes database connection +static bool account_db_sql_init(AccountDB* self) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + struct Sql *sql_handle = NULL; + + nullpo_ret(db); + + 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); + + Sql_HerculesUpdateCheck(db->accounts); +#ifdef CONSOLE_INPUT + console->input->setSQL(db->accounts); +#endif + return true; +} + +/// disconnects from database +static void account_db_sql_destroy(AccountDB* self) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + nullpo_retv(db); + SQL->Free(db->accounts); + db->accounts = NULL; + aFree(db); +} + +/// Gets a property from this database. +static bool account_db_sql_get_property(AccountDB* self, const char* key, char* buf, size_t buflen) +{ + /* TODO: + * This functionality is not being used as of now, it was removed in + * commit 5479f9631f8579d03fbfd14d8a49c7976226a156, it is meant to get + * engine properties when more than one engine is available. I'll + * re-add it as soon as I can, following the new standards. If anyone + * is interested in this functionality you can contact me in our boards + * and I'll try to add it sooner (Pan) [Panikon] + */ +#if 0 + AccountDB_SQL* db = (AccountDB_SQL*)self; + const char* signature; + + nullpo_ret(db); + nullpo_ret(key); + nullpo_ret(buf); + signature = "engine."; + if( strncmpi(key, signature, strlen(signature)) == 0 ) + { + key += strlen(signature); + if( strcmpi(key, "name") == 0 ) + safesnprintf(buf, buflen, "sql"); + else + if( strcmpi(key, "version") == 0 ) + safesnprintf(buf, buflen, "%d", ACCOUNT_SQL_DB_VERSION); + else + if( strcmpi(key, "comment") == 0 ) + safesnprintf(buf, buflen, "SQL Account Database"); + else + return false;// not found + return true; + } + + signature = "account.sql."; + if( strncmpi(key, signature, strlen(signature)) == 0 ) + { + 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, "global_acc_reg_str_db") == 0 ) + safesnprintf(buf, buflen, "%s", db->global_acc_reg_str_db); + else + if( strcmpi(key, "global_acc_reg_num_db") == 0 ) + safesnprintf(buf, buflen, "%s", db->global_acc_reg_num_db); + else + return false;// not found + return true; + } + + return false;// not found +#endif // 0 + return false; +} + +/** + * Reads the 'inter_configuration' config file and initializes required variables. + * + * @param db Self. + * @param filename Path to configuration file + * @param imported Whether the current config is imported from another file. + * + * @retval false in case of error. + */ +bool account_db_read_inter(AccountDB_SQL *db, const char *filename, bool imported) +{ + struct config_t config; + struct config_setting_t *setting = NULL; + + nullpo_retr(false, db); + nullpo_retr(false, filename); + + if (!libconfig->load_file(&config, filename)) + return false; // Error message is already shown by libconfig->load_file + + if ((setting = libconfig->lookup(&config, "inter_configuration/database_names")) == NULL) { + libconfig->destroy(&config); + if (imported) + return true; + ShowError("account_db_sql_set_property: inter_configuration/database_names was not found!\n"); + return false; + } + libconfig->setting_lookup_mutable_string(setting, "account_db", db->account_db, sizeof(db->account_db)); + + if ((setting = libconfig->lookup(&config, "inter_configuration/database_names/registry")) == NULL) { + libconfig->destroy(&config); + if (imported) + return true; + ShowError("account_db_sql_set_property: inter_configuration/database_names/registry was not found!\n"); + return false; + } + libconfig->setting_lookup_mutable_string(setting, "global_acc_reg_str_db", db->global_acc_reg_str_db, sizeof(db->global_acc_reg_str_db)); + libconfig->setting_lookup_mutable_string(setting, "global_acc_reg_num_db", db->global_acc_reg_num_db, sizeof(db->global_acc_reg_num_db)); + + // TODO: Proper import mechanism for this file + + libconfig->destroy(&config); + return true; +} + +/** + * Loads the sql configuration. + * + * @param self Self. + * @param config The current config being parsed. + * @param imported Whether the current config is imported from another file. + * + * @retval false in case of error. + */ +static bool account_db_sql_set_property(AccountDB* self, struct config_t *config, bool imported) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + struct config_setting_t *setting = NULL; + + nullpo_ret(db); + nullpo_ret(config); + + if ((setting = libconfig->lookup(config, "login_configuration/account/sql_connection")) == NULL) { + if (imported) + return true; + ShowError("account_db_sql_set_property: login_configuration/account/sql_connection was not found!\n"); + ShowWarning("account_db_sql_set_property: Defaulting sql_connection...\n"); + return false; + } + + libconfig->setting_lookup_mutable_string(setting, "db_hostname", db->db_hostname, sizeof(db->db_hostname)); + libconfig->setting_lookup_mutable_string(setting, "db_username", db->db_username, sizeof(db->db_username)); + libconfig->setting_lookup_mutable_string(setting, "db_password", db->db_password, sizeof(db->db_password)); + libconfig->setting_lookup_mutable_string(setting, "db_database", db->db_database, sizeof(db->db_database)); + libconfig->setting_lookup_mutable_string(setting, "codepage", db->codepage, sizeof(db->codepage)); // FIXME: Why do we need both codepage and default_codepage? + libconfig->setting_lookup_uint16(setting, "db_port", &db->db_port); + libconfig->setting_lookup_bool_real(setting, "case_sensitive", &db->case_sensitive); + + account_db_read_inter(db, "conf/common/inter-server.conf", imported); + + 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; + struct Sql *sql_handle; + + // decide on the account id to assign + int account_id; + nullpo_ret(db); + nullpo_ret(acc); + sql_handle = db->accounts; + 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; + struct Sql *sql_handle; + bool result = false; + + nullpo_ret(db); + sql_handle = db->accounts; + 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->global_acc_reg_num_db, account_id) + || SQL_SUCCESS != SQL->Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->global_acc_reg_str_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; + struct Sql *sql_handle; + char esc_userid[2*NAME_LENGTH+1]; + int account_id; + char* data; + + nullpo_ret(db); + sql_handle = db->accounts; + 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 - duplicate 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; + + nullpo_retr(NULL, db); + 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; + struct Sql *sql_handle; + char* data; + + nullpo_ret(iter); + db = (AccountDB_SQL*)iter->db; + nullpo_ret(db); + sql_handle = db->accounts; + // 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 + int account_id; + 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) +{ + struct Sql *sql_handle; + char* data; + + nullpo_ret(db); + nullpo_ret(acc); + sql_handle = db->accounts; + // retrieve login entry for the specified account + if( SQL_ERROR == SQL->Query(sql_handle, + "SELECT `account_id`,`userid`,`user_pass`,`sex`,`email`,`group_id`,`state`,`unban_time`,`expiration_time`,`logincount`,`lastlogin`,`last_ip`,`birthdate`,`character_slots`,`pincode`,`pincode_change` 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->group_id = atoi(data); + SQL->GetData(sql_handle, 6, &data, NULL); acc->state = (unsigned int)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 = (unsigned int)strtoul(data, NULL, 10); + SQL->GetData(sql_handle, 10, &data, NULL); safestrncpy(acc->lastlogin, data != NULL ? data : "(never)", sizeof(acc->lastlogin)); + SQL->GetData(sql_handle, 11, &data, NULL); safestrncpy(acc->last_ip, data, sizeof(acc->last_ip)); + SQL->GetData(sql_handle, 12, &data, NULL); safestrncpy(acc->birthdate, data != NULL ? data : "0000-00-00", sizeof(acc->birthdate)); + SQL->GetData(sql_handle, 13, &data, NULL); acc->char_slots = (uint8)atoi(data); + SQL->GetData(sql_handle, 14, &data, NULL); safestrncpy(acc->pincode, data, sizeof(acc->pincode)); + SQL->GetData(sql_handle, 15, &data, NULL); acc->pincode_change = (unsigned int)atol(data); + + SQL->FreeResult(sql_handle); + + return true; +} + +static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new) +{ + struct Sql *sql_handle; + struct SqlStmt *stmt; + bool result = false; + + nullpo_ret(db); + nullpo_ret(acc); + sql_handle = db->accounts; + stmt = SQL->StmtMalloc(sql_handle); + + // 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 != SQL->StmtPrepare(stmt, + "INSERT INTO `%s` (`account_id`, `userid`, `user_pass`, `sex`, `email`, `group_id`, `state`, `unban_time`, `expiration_time`, `logincount`, `lastlogin`, `last_ip`, `birthdate`, `character_slots`, `pincode`, `pincode_change`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + db->account_db) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 0, SQLDT_INT, &acc->account_id, sizeof acc->account_id) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 1, SQLDT_STRING, acc->userid, strlen(acc->userid)) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 2, SQLDT_STRING, acc->pass, strlen(acc->pass)) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 3, SQLDT_ENUM, &acc->sex, sizeof acc->sex) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 4, SQLDT_STRING, &acc->email, strlen(acc->email)) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 5, SQLDT_INT, &acc->group_id, sizeof acc->group_id) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 6, SQLDT_UINT, &acc->state, sizeof acc->state) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 7, SQLDT_TIME, &acc->unban_time, sizeof acc->unban_time) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 8, SQLDT_TIME, &acc->expiration_time, sizeof acc->expiration_time) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 9, SQLDT_UINT, &acc->logincount, sizeof acc->logincount) + || SQL_SUCCESS != (acc->lastlogin[0] < '1' || acc->lastlogin[0] > '9' ? + SQL->StmtBindParam(stmt, 10, SQLDT_NULL, NULL, 0) : + SQL->StmtBindParam(stmt, 10, SQLDT_STRING, &acc->lastlogin, strlen(acc->lastlogin)) + ) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 11, SQLDT_STRING, &acc->last_ip, strlen(acc->last_ip)) + || SQL_SUCCESS != (acc->birthdate[0] == '0' ? + SQL->StmtBindParam(stmt, 12, SQLDT_NULL, NULL, 0) : + SQL->StmtBindParam(stmt, 12, SQLDT_STRING, &acc->birthdate, strlen(acc->birthdate)) + ) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 13, SQLDT_UINT8, &acc->char_slots, sizeof acc->char_slots) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 14, SQLDT_STRING, &acc->pincode, strlen(acc->pincode)) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 15, SQLDT_UINT, &acc->pincode_change, sizeof acc->pincode_change) + || SQL_SUCCESS != SQL->StmtExecute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } else {// update account table + if( SQL_SUCCESS != SQL->StmtPrepare(stmt, "UPDATE `%s` SET `userid`=?,`user_pass`=?,`sex`=?,`email`=?,`group_id`=?,`state`=?,`unban_time`=?,`expiration_time`=?,`logincount`=?,`lastlogin`=?,`last_ip`=?,`birthdate`=?,`character_slots`=?,`pincode`=?,`pincode_change`=? WHERE `account_id` = '%d'", db->account_db, acc->account_id) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 0, SQLDT_STRING, acc->userid, strlen(acc->userid)) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 1, SQLDT_STRING, acc->pass, strlen(acc->pass)) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 2, SQLDT_ENUM, &acc->sex, sizeof acc->sex) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 3, SQLDT_STRING, acc->email, strlen(acc->email)) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 4, SQLDT_INT, &acc->group_id, sizeof acc->group_id) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 5, SQLDT_UINT, &acc->state, sizeof acc->state) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 6, SQLDT_TIME, &acc->unban_time, sizeof acc->unban_time) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 7, SQLDT_TIME, &acc->expiration_time, sizeof acc->expiration_time) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 8, SQLDT_UINT, &acc->logincount, sizeof acc->logincount) + || SQL_SUCCESS != (acc->lastlogin[0] < '1' || acc->lastlogin[0] > '9' ? + SQL->StmtBindParam(stmt, 9, SQLDT_NULL, NULL, 0) : + SQL->StmtBindParam(stmt, 9, SQLDT_STRING, &acc->lastlogin, strlen(acc->lastlogin)) + ) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 10, SQLDT_STRING, &acc->last_ip, strlen(acc->last_ip)) + || SQL_SUCCESS != (acc->birthdate[0] == '0' ? + SQL->StmtBindParam(stmt, 11, SQLDT_NULL, NULL, 0) : + SQL->StmtBindParam(stmt, 11, SQLDT_STRING, &acc->birthdate, strlen(acc->birthdate)) + ) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 12, SQLDT_UINT8, &acc->char_slots, sizeof acc->char_slots) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 13, SQLDT_STRING, &acc->pincode, strlen(acc->pincode)) + || SQL_SUCCESS != SQL->StmtBindParam(stmt, 14, SQLDT_UINT, &acc->pincode_change, sizeof acc->pincode_change) + || SQL_SUCCESS != SQL->StmtExecute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + 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") ); + SQL->StmtFree(stmt); + + return result; +} + +struct Sql *account_db_sql_up(AccountDB* self) +{ + AccountDB_SQL* db = (AccountDB_SQL*)self; + return db ? db->accounts : NULL; +} +void mmo_save_accreg2(AccountDB* self, int fd, int account_id, int char_id) +{ + struct Sql *sql_handle; + AccountDB_SQL* db = (AccountDB_SQL*)self; + int count = RFIFOW(fd, 12); + + nullpo_retv(db); + sql_handle = db->accounts; + if (count) { + int cursor = 14, i; + char key[SCRIPT_VARNAME_LENGTH+1], sval[254]; + + for (i = 0; i < count; i++) { + unsigned int index; + int len = RFIFOB(fd, cursor); + safestrncpy(key, RFIFOP(fd, cursor + 1), min((int)sizeof(key), len)); + cursor += len + 1; + + index = RFIFOL(fd, cursor); + cursor += 4; + + switch (RFIFOB(fd, cursor++)) { + /* int */ + case 0: + if( SQL_ERROR == SQL->Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%u')", db->global_acc_reg_num_db, account_id, key, index, RFIFOL(fd, cursor)) ) + Sql_ShowDebug(sql_handle); + cursor += 4; + break; + case 1: + if( SQL_ERROR == SQL->Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", db->global_acc_reg_num_db, account_id, key, index) ) + Sql_ShowDebug(sql_handle); + break; + /* str */ + case 2: + len = RFIFOB(fd, cursor); + safestrncpy(sval, RFIFOP(fd, cursor + 1), min((int)sizeof(sval), len)); + cursor += len + 1; + if( SQL_ERROR == SQL->Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%s')", db->global_acc_reg_str_db, account_id, key, index, sval) ) + Sql_ShowDebug(sql_handle); + break; + case 3: + if( SQL_ERROR == SQL->Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", db->global_acc_reg_str_db, account_id, key, index) ) + Sql_ShowDebug(sql_handle); + break; + default: + ShowError("mmo_save_accreg2: DA HOO UNKNOWN TYPE %d\n",RFIFOB(fd, cursor - 1)); + return; + } + } + } +} + +void mmo_send_accreg2(AccountDB* self, int fd, int account_id, int char_id) +{ + struct Sql *sql_handle; + AccountDB_SQL* db = (AccountDB_SQL*)self; + char* data; + int plen = 0; + size_t len; + + nullpo_retv(db); + sql_handle = db->accounts; + if( SQL_ERROR == SQL->Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", db->global_acc_reg_str_db, account_id) ) + Sql_ShowDebug(sql_handle); + + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x3804; + /* 0x2 = length, set prior to being sent */ + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */ + WFIFOB(fd, 13) = 1;/* is string type */ + WFIFOW(fd, 14) = 0;/* count */ + plen = 16; + + /** + * Vessel! + * + * str type + * { keyLength(B), key(<keyLength>), index(L), valLength(B), val(<valLength>) } + **/ + while ( SQL_SUCCESS == SQL->NextRow(sql_handle) ) { + SQL->GetData(sql_handle, 0, &data, NULL); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 32 */ + plen += 1; + + safestrncpy(WFIFOP(fd,plen), data, len); + plen += len; + + SQL->GetData(sql_handle, 1, &data, NULL); + + WFIFOL(fd, plen) = (unsigned int)atol(data); + plen += 4; + + SQL->GetData(sql_handle, 2, &data, NULL); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 254 */ + plen += 1; + + safestrncpy(WFIFOP(fd,plen), data, len); + plen += len; + + WFIFOW(fd, 14) += 1; + + if( plen > 60000 ) { + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + /* prepare follow up */ + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x3804; + /* 0x2 = length, set prior to being sent */ + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */ + WFIFOB(fd, 13) = 1;/* is string type */ + WFIFOW(fd, 14) = 0;/* count */ + plen = 16; + } + } + + /* mark & go. */ + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + SQL->FreeResult(sql_handle); + + if( SQL_ERROR == SQL->Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", db->global_acc_reg_num_db, account_id) ) + Sql_ShowDebug(sql_handle); + + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x3804; + /* 0x2 = length, set prior to being sent */ + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */ + WFIFOB(fd, 13) = 0;/* is int type */ + WFIFOW(fd, 14) = 0;/* count */ + plen = 16; + + /** + * Vessel! + * + * int type + * { keyLength(B), key(<keyLength>), index(L), value(L) } + **/ + while ( SQL_SUCCESS == SQL->NextRow(sql_handle) ) { + SQL->GetData(sql_handle, 0, &data, NULL); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 32 */ + plen += 1; + + safestrncpy(WFIFOP(fd,plen), data, len); + plen += len; + + SQL->GetData(sql_handle, 1, &data, NULL); + + WFIFOL(fd, plen) = (unsigned int)atol(data); + plen += 4; + + SQL->GetData(sql_handle, 2, &data, NULL); + + WFIFOL(fd, plen) = atoi(data); + plen += 4; + + WFIFOW(fd, 14) += 1; + + if( plen > 60000 ) { + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + /* prepare follow up */ + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x3804; + /* 0x2 = length, set prior to being sent */ + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */ + WFIFOB(fd, 13) = 0;/* is int type */ + WFIFOW(fd, 14) = 0;/* count */ + + plen = 16; + } + } + + /* mark as complete & go. */ + WFIFOB(fd, 12) = 1; + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + SQL->FreeResult(sql_handle); +} |