account.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953
  1. // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
  2. // For more information, see LICENCE in the main folder
  3. #include "account.hpp"
  4. #include <algorithm> //min / max
  5. #include <cstdlib>
  6. #include <cstring>
  7. #include <common/malloc.hpp>
  8. #include <common/mmo.hpp>
  9. #include <common/showmsg.hpp>
  10. #include <common/socket.hpp>
  11. #include <common/sql.hpp>
  12. #include <common/strlib.hpp>
  13. #include "login.hpp" // login_config
  14. /// global defines
  15. /// internal structure
  16. typedef struct AccountDB_SQL {
  17. AccountDB vtable; // public interface
  18. Sql* accounts; // SQL handle accounts storage
  19. std::string db_hostname = "127.0.0.1";
  20. uint16 db_port = 3306;
  21. std::string db_username = "ragnarok";
  22. std::string db_password = "";
  23. std::string db_database = "ragnarok";
  24. std::string codepage = "";
  25. // other settings
  26. bool case_sensitive;
  27. //table name
  28. char account_db[32];
  29. char global_acc_reg_num_table[32];
  30. char global_acc_reg_str_table[32];
  31. } AccountDB_SQL;
  32. /// internal structure
  33. typedef struct AccountDBIterator_SQL {
  34. AccountDBIterator vtable; // public interface
  35. AccountDB_SQL* db;
  36. int32 last_account_id;
  37. } AccountDBIterator_SQL;
  38. /// internal functions
  39. static bool account_db_sql_init(AccountDB* self);
  40. static void account_db_sql_destroy(AccountDB* self);
  41. static bool account_db_sql_get_property(AccountDB* self, const char* key, char* buf, size_t buflen);
  42. static bool account_db_sql_set_property(AccountDB* self, const char* option, const char* value);
  43. static bool account_db_sql_create(AccountDB* self, struct mmo_account* acc);
  44. static bool account_db_sql_remove(AccountDB* self, const uint32 account_id);
  45. static bool account_db_sql_enable_webtoken( AccountDB* self, const uint32 account_id );
  46. static bool account_db_sql_disable_webtoken( AccountDB* self, const uint32 account_id );
  47. static bool account_db_sql_remove_webtokens( AccountDB* self );
  48. static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc, bool refresh_token);
  49. static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const uint32 account_id);
  50. static bool account_db_sql_load_str(AccountDB* self, struct mmo_account* acc, const char* userid);
  51. static AccountDBIterator* account_db_sql_iterator(AccountDB* self);
  52. static void account_db_sql_iter_destroy(AccountDBIterator* self);
  53. static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account* acc);
  54. static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, uint32 account_id);
  55. static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new, bool refresh_token);
  56. /// public constructor
  57. AccountDB* account_db_sql(void) {
  58. AccountDB_SQL* db = (AccountDB_SQL*)aCalloc(1, sizeof(AccountDB_SQL));
  59. new(db) AccountDB_SQL();
  60. // set up the vtable
  61. db->vtable.init = &account_db_sql_init;
  62. db->vtable.destroy = &account_db_sql_destroy;
  63. db->vtable.get_property = &account_db_sql_get_property;
  64. db->vtable.set_property = &account_db_sql_set_property;
  65. db->vtable.save = &account_db_sql_save;
  66. db->vtable.create = &account_db_sql_create;
  67. db->vtable.remove = &account_db_sql_remove;
  68. db->vtable.enable_webtoken = &account_db_sql_enable_webtoken;
  69. db->vtable.disable_webtoken = &account_db_sql_disable_webtoken;
  70. db->vtable.remove_webtokens = &account_db_sql_remove_webtokens;
  71. db->vtable.load_num = &account_db_sql_load_num;
  72. db->vtable.load_str = &account_db_sql_load_str;
  73. db->vtable.iterator = &account_db_sql_iterator;
  74. // initialize to default values
  75. db->accounts = nullptr;
  76. // other settings
  77. db->case_sensitive = false;
  78. safestrncpy(db->account_db, "login", sizeof(db->account_db));
  79. safestrncpy(db->global_acc_reg_num_table, "global_acc_reg_num", sizeof(db->global_acc_reg_num_table));
  80. safestrncpy(db->global_acc_reg_str_table, "global_acc_reg_str", sizeof(db->global_acc_reg_str_table));
  81. return &db->vtable;
  82. }
  83. /* ------------------------------------------------------------------------- */
  84. /**
  85. * Establish the database connection.
  86. * @param self: pointer to db
  87. */
  88. static bool account_db_sql_init(AccountDB* self) {
  89. AccountDB_SQL* db = (AccountDB_SQL*)self;
  90. Sql* sql_handle;
  91. db->accounts = Sql_Malloc();
  92. sql_handle = db->accounts;
  93. if( SQL_ERROR == Sql_Connect(sql_handle, db->db_username.c_str(), db->db_password.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str()) )
  94. {
  95. ShowError("Couldn't connect with uname='%s',host='%s',port='%hu',database='%s'\n",
  96. db->db_username.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str());
  97. Sql_ShowDebug(sql_handle);
  98. Sql_Free(db->accounts);
  99. db->accounts = nullptr;
  100. return false;
  101. }
  102. if( !db->codepage.empty() && SQL_ERROR == Sql_SetEncoding(sql_handle, db->codepage.c_str()) )
  103. Sql_ShowDebug(sql_handle);
  104. self->remove_webtokens( self );
  105. return true;
  106. }
  107. /**
  108. * Destroy the database and close the connection to it.
  109. * @param self: pointer to db
  110. */
  111. static void account_db_sql_destroy(AccountDB* self){
  112. AccountDB_SQL* db = (AccountDB_SQL*)self;
  113. if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token` = NULL", db->account_db ) ){
  114. Sql_ShowDebug( db->accounts );
  115. }
  116. Sql_Free(db->accounts);
  117. db->accounts = nullptr;
  118. db->~AccountDB_SQL();
  119. aFree(db);
  120. }
  121. /**
  122. * Get configuration information into buf.
  123. * If the option is supported, adjust the internal state.
  124. * @param self: pointer to db
  125. * @param key: config keyword
  126. * @param buf: value set of the keyword
  127. * @param buflen: size of buffer to avoid out of bound
  128. * @return true if successful, false if something has failed
  129. */
  130. static bool account_db_sql_get_property(AccountDB* self, const char* key, char* buf, size_t buflen)
  131. {
  132. AccountDB_SQL* db = (AccountDB_SQL*)self;
  133. const char* signature;
  134. signature = "login_server_";
  135. if( strncmpi(key, signature, strlen(signature)) == 0 ) {
  136. key += strlen(signature);
  137. if( strcmpi(key, "ip") == 0 )
  138. safesnprintf(buf, buflen, "%s", db->db_hostname.c_str());
  139. else
  140. if( strcmpi(key, "port") == 0 )
  141. safesnprintf(buf, buflen, "%hu", db->db_port);
  142. else
  143. if( strcmpi(key, "id") == 0 )
  144. safesnprintf(buf, buflen, "%s", db->db_username.c_str());
  145. else
  146. if( strcmpi(key, "pw") == 0 )
  147. safesnprintf(buf, buflen, "%s", db->db_password.c_str());
  148. else
  149. if( strcmpi(key, "db") == 0 )
  150. safesnprintf(buf, buflen, "%s", db->db_database.c_str());
  151. else
  152. if( strcmpi(key, "account_db") == 0 )
  153. safesnprintf(buf, buflen, "%s", db->account_db);
  154. else
  155. if( strcmpi(key, "global_acc_reg_str_table") == 0 )
  156. safesnprintf(buf, buflen, "%s", db->global_acc_reg_str_table);
  157. else
  158. if( strcmpi(key, "global_acc_reg_num_table") == 0 )
  159. safesnprintf(buf, buflen, "%s", db->global_acc_reg_num_table);
  160. else
  161. return false;// not found
  162. return true;
  163. }
  164. signature = "login_";
  165. if( strncmpi(key, signature, strlen(signature)) == 0 ) {
  166. key += strlen(signature);
  167. if( strcmpi(key, "codepage") == 0 )
  168. safesnprintf(buf, buflen, "%s", db->codepage.c_str());
  169. else
  170. if( strcmpi(key, "case_sensitive") == 0 )
  171. safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0));
  172. else
  173. return false;// not found
  174. return true;
  175. }
  176. return false;// not found
  177. }
  178. /**
  179. * Read and set configuration.
  180. * If the option is supported, adjust the internal state.
  181. * @param self: pointer to db
  182. * @param key: config keyword
  183. * @param value: config value for keyword
  184. * @return true if successful, false if something has failed
  185. */
  186. static bool account_db_sql_set_property(AccountDB* self, const char* key, const char* value) {
  187. AccountDB_SQL* db = (AccountDB_SQL*)self;
  188. const char* signature;
  189. signature = "login_server_";
  190. if( strncmp(key, signature, strlen(signature)) == 0 ) {
  191. key += strlen(signature);
  192. if( strcmpi(key, "ip") == 0 )
  193. db->db_hostname = value;
  194. else
  195. if( strcmpi(key, "port") == 0 )
  196. db->db_port = (uint16)strtoul( value, nullptr, 10 );
  197. else
  198. if( strcmpi(key, "id") == 0 )
  199. db->db_username = value;
  200. else
  201. if( strcmpi(key, "pw") == 0 )
  202. db->db_password = value;
  203. else
  204. if( strcmpi(key, "db") == 0 )
  205. db->db_database = value;
  206. else
  207. if( strcmpi(key, "account_db") == 0 )
  208. safestrncpy(db->account_db, value, sizeof(db->account_db));
  209. else
  210. if( strcmpi(key, "global_acc_reg_str_table") == 0 )
  211. safestrncpy(db->global_acc_reg_str_table, value, sizeof(db->global_acc_reg_str_table));
  212. else
  213. if( strcmpi(key, "global_acc_reg_num_table") == 0 )
  214. safestrncpy(db->global_acc_reg_num_table, value, sizeof(db->global_acc_reg_num_table));
  215. else
  216. return false;// not found
  217. return true;
  218. }
  219. signature = "login_";
  220. if( strncmpi(key, signature, strlen(signature)) == 0 ) {
  221. key += strlen(signature);
  222. if( strcmpi(key, "codepage") == 0 )
  223. db->codepage = value;
  224. else
  225. if( strcmpi(key, "case_sensitive") == 0 )
  226. db->case_sensitive = (config_switch(value)==1);
  227. else
  228. return false;// not found
  229. return true;
  230. }
  231. return false;// not found
  232. }
  233. /**
  234. * Create a new account entry.
  235. * If acc->account_id is -1, the account id will be auto-generated,
  236. * and its value will be written to acc->account_id if everything succeeds.
  237. * @param self: pointer to db
  238. * @param acc: pointer of mmo_account to save
  239. * @return true if successful, false if something has failed
  240. */
  241. static bool account_db_sql_create(AccountDB* self, struct mmo_account* acc) {
  242. AccountDB_SQL* db = (AccountDB_SQL*)self;
  243. Sql* sql_handle = db->accounts;
  244. // decide on the account id to assign
  245. uint32 account_id;
  246. if( acc->account_id != -1 )
  247. {// caller specifies it manually
  248. account_id = acc->account_id;
  249. }
  250. else
  251. {// ask the database
  252. char* data;
  253. size_t len;
  254. if( SQL_SUCCESS != Sql_Query(sql_handle, "SELECT MAX(`account_id`)+1 FROM `%s`", db->account_db) )
  255. {
  256. Sql_ShowDebug(sql_handle);
  257. return false;
  258. }
  259. if( SQL_SUCCESS != Sql_NextRow(sql_handle) )
  260. {
  261. Sql_ShowDebug(sql_handle);
  262. Sql_FreeResult(sql_handle);
  263. return false;
  264. }
  265. Sql_GetData(sql_handle, 0, &data, &len);
  266. account_id = ( data != nullptr ) ? atoi(data) : 0;
  267. Sql_FreeResult(sql_handle);
  268. account_id = max((uint32_t) START_ACCOUNT_NUM, account_id);
  269. }
  270. // zero value is prohibited
  271. if( account_id == 0 )
  272. return false;
  273. // absolute maximum
  274. if( account_id > END_ACCOUNT_NUM )
  275. return false;
  276. // insert the data into the database
  277. acc->account_id = account_id;
  278. return mmo_auth_tosql(db, acc, true, false);
  279. }
  280. /**
  281. * Delete an existing account entry and its regs.
  282. * @param self: pointer to db
  283. * @param account_id: id of user account
  284. * @return true if successful, false if something has failed
  285. */
  286. static bool account_db_sql_remove(AccountDB* self, const uint32 account_id) {
  287. AccountDB_SQL* db = (AccountDB_SQL*)self;
  288. Sql* sql_handle = db->accounts;
  289. bool result = false;
  290. if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION")
  291. || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->account_db, account_id)
  292. || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->global_acc_reg_num_table, account_id)
  293. || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->global_acc_reg_str_table, account_id) )
  294. Sql_ShowDebug(sql_handle);
  295. else
  296. result = true;
  297. result &= ( SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") );
  298. return result;
  299. }
  300. /**
  301. * Update an existing account with the new data provided (both account and regs).
  302. * @param self: pointer to db
  303. * @param acc: pointer of mmo_account to save
  304. * @return true if successful, false if something has failed
  305. */
  306. static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc, bool refresh_token) {
  307. AccountDB_SQL* db = (AccountDB_SQL*)self;
  308. return mmo_auth_tosql(db, acc, false, refresh_token);
  309. }
  310. /**
  311. * Retrieve data from db and store it in the provided data structure.
  312. * Filled data structure is done by delegation to mmo_auth_fromsql.
  313. * @param self: pointer to db
  314. * @param acc: pointer of mmo_account to fill
  315. * @param account_id: id of user account
  316. * @return true if successful, false if something has failed
  317. */
  318. static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const uint32 account_id) {
  319. AccountDB_SQL* db = (AccountDB_SQL*)self;
  320. return mmo_auth_fromsql(db, acc, account_id);
  321. }
  322. /**
  323. * Retrieve data from db and store it in the provided data structure.
  324. * Doesn't actually retrieve data yet: escapes and checks userid, then transforms it to accid for fetching.
  325. * Filled data structure is done by delegation to account_db_sql_load_num.
  326. * @param self: pointer to db
  327. * @param acc: pointer of mmo_account to fill
  328. * @param userid: name of user account
  329. * @return true if successful, false if something has failed
  330. */
  331. static bool account_db_sql_load_str(AccountDB* self, struct mmo_account* acc, const char* userid) {
  332. AccountDB_SQL* db = (AccountDB_SQL*)self;
  333. Sql* sql_handle = db->accounts;
  334. char esc_userid[2*NAME_LENGTH+1];
  335. uint32 account_id;
  336. char* data;
  337. Sql_EscapeString(sql_handle, esc_userid, userid);
  338. // get the list of account IDs for this user ID
  339. if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `userid`= %s '%s'",
  340. db->account_db, (db->case_sensitive ? "BINARY" : ""), esc_userid) )
  341. {
  342. Sql_ShowDebug(sql_handle);
  343. return false;
  344. }
  345. if( Sql_NumRows(sql_handle) > 1 )
  346. {// serious problem - duplicit account
  347. ShowError("account_db_sql_load_str: multiple accounts found when retrieving data for account '%s'!\n", userid);
  348. Sql_FreeResult(sql_handle);
  349. return false;
  350. }
  351. if( SQL_SUCCESS != Sql_NextRow(sql_handle) )
  352. {// no such entry
  353. Sql_FreeResult(sql_handle);
  354. return false;
  355. }
  356. Sql_GetData(sql_handle, 0, &data, nullptr);
  357. account_id = atoi(data);
  358. return account_db_sql_load_num(self, acc, account_id);
  359. }
  360. /**
  361. * Create a new forward iterator.
  362. * @param self: pointer to db iterator
  363. * @return a new db iterator
  364. */
  365. static AccountDBIterator* account_db_sql_iterator(AccountDB* self) {
  366. AccountDB_SQL* db = (AccountDB_SQL*)self;
  367. AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)aCalloc(1, sizeof(AccountDBIterator_SQL));
  368. // set up the vtable
  369. iter->vtable.destroy = &account_db_sql_iter_destroy;
  370. iter->vtable.next = &account_db_sql_iter_next;
  371. // fill data
  372. iter->db = db;
  373. iter->last_account_id = -1;
  374. return &iter->vtable;
  375. }
  376. /**
  377. * Destroys this iterator, releasing all allocated memory (including itself).
  378. * @param self: pointer to db iterator
  379. */
  380. static void account_db_sql_iter_destroy(AccountDBIterator* self) {
  381. AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self;
  382. aFree(iter);
  383. }
  384. /**
  385. * Fetches the next account in the database.
  386. * @param self: pointer to db iterator
  387. * @param acc: pointer of mmo_account to fill
  388. * @return true if next account found and filled, false if something has failed
  389. */
  390. static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account* acc) {
  391. AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self;
  392. AccountDB_SQL* db = iter->db;
  393. Sql* sql_handle = db->accounts;
  394. char* data;
  395. // get next account ID
  396. if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `account_id` > '%d' ORDER BY `account_id` ASC LIMIT 1",
  397. db->account_db, iter->last_account_id) )
  398. {
  399. Sql_ShowDebug(sql_handle);
  400. return false;
  401. }
  402. if( SQL_SUCCESS == Sql_NextRow(sql_handle) &&
  403. SQL_SUCCESS == Sql_GetData(sql_handle, 0, &data, nullptr) &&
  404. data != nullptr )
  405. {// get account data
  406. uint32 account_id;
  407. account_id = atoi(data);
  408. if( mmo_auth_fromsql(db, acc, account_id) )
  409. {
  410. iter->last_account_id = account_id;
  411. Sql_FreeResult(sql_handle);
  412. return true;
  413. }
  414. }
  415. Sql_FreeResult(sql_handle);
  416. return false;
  417. }
  418. /**
  419. * Fetch a struct mmo_account from sql, excluding web_auth_token.
  420. * @param db: pointer to db
  421. * @param acc: pointer of mmo_account to fill
  422. * @param account_id: id of user account to take data from
  423. * @return true if successful, false if something has failed
  424. */
  425. static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, uint32 account_id) {
  426. Sql* sql_handle = db->accounts;
  427. char* data;
  428. // retrieve login entry for the specified account
  429. if( SQL_ERROR == Sql_Query(sql_handle,
  430. #ifdef VIP_ENABLE
  431. "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`, `vip_time`, `old_group` FROM `%s` WHERE `account_id` = %d",
  432. #else
  433. "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",
  434. #endif
  435. db->account_db, account_id )
  436. ) {
  437. Sql_ShowDebug(sql_handle);
  438. return false;
  439. }
  440. if( SQL_SUCCESS != Sql_NextRow(sql_handle) )
  441. {// no such entry
  442. Sql_FreeResult(sql_handle);
  443. return false;
  444. }
  445. Sql_GetData(sql_handle, 0, &data, nullptr); acc->account_id = atoi(data);
  446. Sql_GetData(sql_handle, 1, &data, nullptr); safestrncpy(acc->userid, data, sizeof(acc->userid));
  447. Sql_GetData(sql_handle, 2, &data, nullptr); safestrncpy(acc->pass, data, sizeof(acc->pass));
  448. Sql_GetData(sql_handle, 3, &data, nullptr); acc->sex = data[0];
  449. Sql_GetData(sql_handle, 4, &data, nullptr); safestrncpy(acc->email, data, sizeof(acc->email));
  450. Sql_GetData(sql_handle, 5, &data, nullptr); acc->group_id = (uint32)atoi(data);
  451. Sql_GetData(sql_handle, 6, &data, nullptr); acc->state = (uint32)strtoul(data, nullptr, 10);
  452. Sql_GetData(sql_handle, 7, &data, nullptr); acc->unban_time = atol(data);
  453. Sql_GetData(sql_handle, 8, &data, nullptr); acc->expiration_time = atol(data);
  454. Sql_GetData(sql_handle, 9, &data, nullptr); acc->logincount = (uint32)strtoul(data, nullptr, 10);
  455. Sql_GetData(sql_handle, 10, &data, nullptr); safestrncpy(acc->lastlogin, data==nullptr?"":data, sizeof(acc->lastlogin));
  456. Sql_GetData(sql_handle, 11, &data, nullptr); safestrncpy(acc->last_ip, data, sizeof(acc->last_ip));
  457. Sql_GetData(sql_handle, 12, &data, nullptr); safestrncpy(acc->birthdate, data==nullptr?"":data, sizeof(acc->birthdate));
  458. Sql_GetData(sql_handle, 13, &data, nullptr); acc->char_slots = (uint8) atoi(data);
  459. Sql_GetData(sql_handle, 14, &data, nullptr); safestrncpy(acc->pincode, data, sizeof(acc->pincode));
  460. Sql_GetData(sql_handle, 15, &data, nullptr); acc->pincode_change = atol(data);
  461. #ifdef VIP_ENABLE
  462. Sql_GetData(sql_handle, 16, &data, nullptr); acc->vip_time = atol(data);
  463. Sql_GetData(sql_handle, 17, &data, nullptr); acc->old_group = atoi(data);
  464. #endif
  465. Sql_FreeResult(sql_handle);
  466. acc->web_auth_token[0] = '\0';
  467. if( acc->char_slots > MAX_CHARS ){
  468. ShowError( "Account %s (AID=%u) exceeds MAX_CHARS. Capping...\n", acc->userid, acc->account_id );
  469. acc->char_slots = MAX_CHARS;
  470. }
  471. return true;
  472. }
  473. /**
  474. * Save a struct mmo_account in sql.
  475. * @param db: pointer to db
  476. * @param acc: pointer of mmo_account to save
  477. * @param is_new: if it's a new entry or should we update
  478. * @return true if successful, false if something has failed
  479. */
  480. static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new, bool refresh_token) {
  481. Sql* sql_handle = db->accounts;
  482. SqlStmt stmt{ *sql_handle };
  483. bool result = false;
  484. // try
  485. do
  486. {
  487. if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION") )
  488. {
  489. Sql_ShowDebug(sql_handle);
  490. break;
  491. }
  492. if( is_new )
  493. {// insert into account table
  494. if( SQL_SUCCESS != stmt.Prepare(
  495. #ifdef VIP_ENABLE
  496. "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`, `vip_time`, `old_group` ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
  497. #else
  498. "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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
  499. #endif
  500. db->account_db)
  501. || SQL_SUCCESS != stmt.BindParam( 0, SQLDT_INT32, (void*)&acc->account_id, sizeof(acc->account_id))
  502. || SQL_SUCCESS != stmt.BindParam( 1, SQLDT_STRING, (void*)acc->userid, strlen(acc->userid))
  503. || SQL_SUCCESS != stmt.BindParam( 2, SQLDT_STRING, (void*)acc->pass, strlen(acc->pass))
  504. || SQL_SUCCESS != stmt.BindParam( 3, SQLDT_ENUM, (void*)&acc->sex, sizeof(acc->sex))
  505. || SQL_SUCCESS != stmt.BindParam( 4, SQLDT_STRING, (void*)&acc->email, strlen(acc->email))
  506. || SQL_SUCCESS != stmt.BindParam( 5, SQLDT_INT32, (void*)&acc->group_id, sizeof(acc->group_id))
  507. || SQL_SUCCESS != stmt.BindParam( 6, SQLDT_UINT32, (void*)&acc->state, sizeof(acc->state))
  508. || SQL_SUCCESS != stmt.BindParam( 7, SQLDT_LONG, (void*)&acc->unban_time, sizeof(acc->unban_time))
  509. || SQL_SUCCESS != stmt.BindParam( 8, SQLDT_INT32, (void*)&acc->expiration_time, sizeof(acc->expiration_time))
  510. || SQL_SUCCESS != stmt.BindParam( 9, SQLDT_UINT32, (void*)&acc->logincount, sizeof(acc->logincount))
  511. || SQL_SUCCESS != stmt.BindParam(10, acc->lastlogin[0]?SQLDT_STRING:SQLDT_NULL, (void*)&acc->lastlogin, strlen(acc->lastlogin))
  512. || SQL_SUCCESS != stmt.BindParam(11, SQLDT_STRING, (void*)&acc->last_ip, strlen(acc->last_ip))
  513. || SQL_SUCCESS != stmt.BindParam(12, acc->birthdate[0]?SQLDT_STRING:SQLDT_NULL, (void*)&acc->birthdate, strlen(acc->birthdate))
  514. || SQL_SUCCESS != stmt.BindParam(13, SQLDT_UCHAR, (void*)&acc->char_slots, sizeof(acc->char_slots))
  515. || SQL_SUCCESS != stmt.BindParam(14, SQLDT_STRING, (void*)&acc->pincode, strlen(acc->pincode))
  516. || SQL_SUCCESS != stmt.BindParam(15, SQLDT_LONG, (void*)&acc->pincode_change, sizeof(acc->pincode_change))
  517. #ifdef VIP_ENABLE
  518. || SQL_SUCCESS != stmt.BindParam(16, SQLDT_LONG, (void*)&acc->vip_time, sizeof(acc->vip_time))
  519. || SQL_SUCCESS != stmt.BindParam(17, SQLDT_INT32, (void*)&acc->old_group, sizeof(acc->old_group))
  520. #endif
  521. || SQL_SUCCESS != stmt.Execute()
  522. ) {
  523. SqlStmt_ShowDebug(stmt);
  524. break;
  525. }
  526. }
  527. else
  528. {// update account table
  529. if( SQL_SUCCESS != stmt.Prepare(
  530. #ifdef VIP_ENABLE
  531. "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`=?, `vip_time`=?, `old_group`=? WHERE `account_id` = '%d'",
  532. #else
  533. "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'",
  534. #endif
  535. db->account_db, acc->account_id)
  536. || SQL_SUCCESS != stmt.BindParam( 0, SQLDT_STRING, (void*)acc->userid, strlen(acc->userid))
  537. || SQL_SUCCESS != stmt.BindParam( 1, SQLDT_STRING, (void*)acc->pass, strlen(acc->pass))
  538. || SQL_SUCCESS != stmt.BindParam( 2, SQLDT_ENUM, (void*)&acc->sex, sizeof(acc->sex))
  539. || SQL_SUCCESS != stmt.BindParam( 3, SQLDT_STRING, (void*)acc->email, strlen(acc->email))
  540. || SQL_SUCCESS != stmt.BindParam( 4, SQLDT_INT32, (void*)&acc->group_id, sizeof(acc->group_id))
  541. || SQL_SUCCESS != stmt.BindParam( 5, SQLDT_UINT32, (void*)&acc->state, sizeof(acc->state))
  542. || SQL_SUCCESS != stmt.BindParam( 6, SQLDT_LONG, (void*)&acc->unban_time, sizeof(acc->unban_time))
  543. || SQL_SUCCESS != stmt.BindParam( 7, SQLDT_LONG, (void*)&acc->expiration_time, sizeof(acc->expiration_time))
  544. || SQL_SUCCESS != stmt.BindParam( 8, SQLDT_UINT32, (void*)&acc->logincount, sizeof(acc->logincount))
  545. || SQL_SUCCESS != stmt.BindParam( 9, acc->lastlogin[0]?SQLDT_STRING:SQLDT_NULL, (void*)&acc->lastlogin, strlen(acc->lastlogin))
  546. || SQL_SUCCESS != stmt.BindParam(10, SQLDT_STRING, (void*)&acc->last_ip, strlen(acc->last_ip))
  547. || SQL_SUCCESS != stmt.BindParam(11, acc->birthdate[0]?SQLDT_STRING:SQLDT_NULL, (void*)&acc->birthdate, strlen(acc->birthdate))
  548. || SQL_SUCCESS != stmt.BindParam(12, SQLDT_UCHAR, (void*)&acc->char_slots, sizeof(acc->char_slots))
  549. || SQL_SUCCESS != stmt.BindParam(13, SQLDT_STRING, (void*)&acc->pincode, strlen(acc->pincode))
  550. || SQL_SUCCESS != stmt.BindParam(14, SQLDT_LONG, (void*)&acc->pincode_change, sizeof(acc->pincode_change))
  551. #ifdef VIP_ENABLE
  552. || SQL_SUCCESS != stmt.BindParam(15, SQLDT_LONG, (void*)&acc->vip_time, sizeof(acc->vip_time))
  553. || SQL_SUCCESS != stmt.BindParam(16, SQLDT_INT32, (void*)&acc->old_group, sizeof(acc->old_group))
  554. #endif
  555. || SQL_SUCCESS != stmt.Execute()
  556. ) {
  557. SqlStmt_ShowDebug(stmt);
  558. break;
  559. }
  560. }
  561. if( acc->sex != 'S' && login_config.use_web_auth_token && refresh_token ){
  562. static bool initialized = false;
  563. static const char* query;
  564. // Pseudo Scope to break out
  565. while( !initialized ){
  566. if( SQL_SUCCESS == Sql_Query( sql_handle, "SELECT SHA2( 'test', 256 )" ) ){
  567. query = "UPDATE `%s` SET `web_auth_token` = LEFT( SHA2( CONCAT( UUID(), RAND() ), 256 ), %d ), `web_auth_token_enabled` = '1' WHERE `account_id` = '%d'";
  568. initialized = true;
  569. break;
  570. }
  571. if( SQL_SUCCESS == Sql_Query( sql_handle, "SELECT MD5( 'test' )" ) ){
  572. query = "UPDATE `%s` SET `web_auth_token` = LEFT( MD5( CONCAT( UUID(), RAND() ) ), %d ), `web_auth_token_enabled` = '1' WHERE `account_id` = '%d'";
  573. initialized = true;
  574. break;
  575. }
  576. ShowWarning( "Your MySQL does not support SHA2 and MD5 - no hashing will be used for login token creation.\n" );
  577. ShowWarning( "If you are using an old version of MySQL consider upgrading to a newer release.\n" );
  578. query = "UPDATE `%s` SET `web_auth_token` = LEFT( CONCAT( UUID(), RAND() ), %d ), `web_auth_token_enabled` = '1' WHERE `account_id` = '%d'";
  579. initialized = true;
  580. break;
  581. }
  582. const int32 MAX_RETRIES = 20;
  583. int32 i = 0;
  584. bool success = false;
  585. // Retry it for a maximum number of retries
  586. do{
  587. if( SQL_SUCCESS == Sql_Query( sql_handle, query, db->account_db, WEB_AUTH_TOKEN_LENGTH - 1, acc->account_id ) ){
  588. success = true;
  589. break;
  590. }
  591. }while( i < MAX_RETRIES && Sql_GetError( sql_handle ) == 1062 );
  592. if( !success ){
  593. if( i == MAX_RETRIES ){
  594. ShowError( "Failed to generate a unique web_auth_token with %d retries...\n", i );
  595. }else{
  596. Sql_ShowDebug( sql_handle );
  597. }
  598. break;
  599. }
  600. char* data;
  601. size_t len;
  602. if( SQL_SUCCESS != Sql_Query( sql_handle, "SELECT `web_auth_token` from `%s` WHERE `account_id` = '%d'", db->account_db, acc->account_id ) ||
  603. SQL_SUCCESS != Sql_NextRow( sql_handle ) ||
  604. SQL_SUCCESS != Sql_GetData( sql_handle, 0, &data, &len )
  605. ){
  606. Sql_ShowDebug( sql_handle );
  607. break;
  608. }
  609. safestrncpy( (char *)&acc->web_auth_token, data, sizeof( acc->web_auth_token ) );
  610. Sql_FreeResult( sql_handle );
  611. }
  612. // if we got this far, everything was successful
  613. result = true;
  614. } while(0);
  615. // finally
  616. result &= ( SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") );
  617. return result;
  618. }
  619. void mmo_save_global_accreg(AccountDB* self, int32 fd, uint32 account_id, uint32 char_id) {
  620. Sql* sql_handle = ((AccountDB_SQL*)self)->accounts;
  621. AccountDB_SQL* db = (AccountDB_SQL*)self;
  622. uint16 count = RFIFOW(fd, 12);
  623. if (count) {
  624. int32 cursor = 14, i;
  625. char key[32], sval[254], esc_key[32*2+1], esc_sval[254*2+1];
  626. for (i = 0; i < count; i++) {
  627. uint32 index;
  628. safestrncpy(key, RFIFOCP(fd, cursor + 1), RFIFOB(fd, cursor));
  629. Sql_EscapeString(sql_handle, esc_key, key);
  630. cursor += RFIFOB(fd, cursor) + 1;
  631. index = RFIFOL(fd, cursor);
  632. cursor += 4;
  633. switch (RFIFOB(fd, cursor++)) {
  634. // int32
  635. case 0:
  636. if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%" PRIu32 "','%s','%" PRIu32 "','%" PRId64 "')", db->global_acc_reg_num_table, account_id, esc_key, index, RFIFOQ(fd, cursor)) )
  637. Sql_ShowDebug(sql_handle);
  638. cursor += 8;
  639. break;
  640. case 1:
  641. if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%" PRIu32 "' AND `key` = '%s' AND `index` = '%" PRIu32 "' LIMIT 1", db->global_acc_reg_num_table, account_id, esc_key, index) )
  642. Sql_ShowDebug(sql_handle);
  643. break;
  644. // str
  645. case 2:
  646. safestrncpy(sval, RFIFOCP(fd, cursor + 1), RFIFOB(fd, cursor));
  647. cursor += RFIFOB(fd, cursor) + 1;
  648. Sql_EscapeString(sql_handle, esc_sval, sval);
  649. if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%" PRIu32 "','%s','%" PRIu32 "','%s')", db->global_acc_reg_str_table, account_id, esc_key, index, esc_sval) )
  650. Sql_ShowDebug(sql_handle);
  651. break;
  652. case 3:
  653. if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%" PRIu32 "' AND `key` = '%s' AND `index` = '%" PRIu32 "' LIMIT 1", db->global_acc_reg_str_table, account_id, esc_key, index) )
  654. Sql_ShowDebug(sql_handle);
  655. break;
  656. default:
  657. ShowError("mmo_save_global_accreg: unknown type %d\n",RFIFOB(fd, cursor - 1));
  658. return;
  659. }
  660. }
  661. }
  662. }
  663. void mmo_send_global_accreg(AccountDB* self, int32 fd, uint32 account_id, uint32 char_id) {
  664. Sql* sql_handle = ((AccountDB_SQL*)self)->accounts;
  665. AccountDB_SQL* db = (AccountDB_SQL*)self;
  666. char* data;
  667. int16 plen = 0;
  668. size_t len;
  669. if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%" PRIu32 "'", db->global_acc_reg_str_table, account_id) )
  670. Sql_ShowDebug(sql_handle);
  671. WFIFOHEAD(fd, 60000 + 300);
  672. WFIFOW(fd, 0) = 0x2726;
  673. // 0x2 = length, set prior to being sent
  674. WFIFOL(fd, 4) = account_id;
  675. WFIFOL(fd, 8) = char_id;
  676. WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
  677. WFIFOB(fd, 13) = 1; // is string type
  678. WFIFOW(fd, 14) = 0; // count
  679. plen = 16;
  680. /**
  681. * Vessel!
  682. *
  683. * str type
  684. * { keyLength(B), key(<keyLength>), index(L), valLength(B), val(<valLength>) }
  685. **/
  686. while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) {
  687. Sql_GetData(sql_handle, 0, &data, nullptr);
  688. len = strlen(data)+1;
  689. WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32
  690. plen += 1;
  691. safestrncpy(WFIFOCP(fd,plen), data, len);
  692. plen += static_cast<decltype(plen)>( len );
  693. Sql_GetData(sql_handle, 1, &data, nullptr);
  694. WFIFOL(fd, plen) = (uint32)atol(data);
  695. plen += 4;
  696. Sql_GetData(sql_handle, 2, &data, nullptr);
  697. len = strlen(data)+1;
  698. WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 254
  699. plen += 1;
  700. safestrncpy(WFIFOCP(fd,plen), data, len);
  701. plen += static_cast<decltype(plen)>( len );
  702. WFIFOW(fd, 14) += 1;
  703. if( plen > 60000 ) {
  704. WFIFOW(fd, 2) = plen;
  705. WFIFOSET(fd, plen);
  706. // prepare follow up
  707. WFIFOHEAD(fd, 60000 + 300);
  708. WFIFOW(fd, 0) = 0x2726;
  709. // 0x2 = length, set prior to being sent
  710. WFIFOL(fd, 4) = account_id;
  711. WFIFOL(fd, 8) = char_id;
  712. WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
  713. WFIFOB(fd, 13) = 1; // is string type
  714. WFIFOW(fd, 14) = 0; // count
  715. plen = 16;
  716. }
  717. }
  718. WFIFOW(fd, 2) = plen;
  719. WFIFOSET(fd, plen);
  720. Sql_FreeResult(sql_handle);
  721. if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%" PRIu32 "'", db->global_acc_reg_num_table, account_id) )
  722. Sql_ShowDebug(sql_handle);
  723. WFIFOHEAD(fd, 60000 + 300);
  724. WFIFOW(fd, 0) = 0x2726;
  725. // 0x2 = length, set prior to being sent
  726. WFIFOL(fd, 4) = account_id;
  727. WFIFOL(fd, 8) = char_id;
  728. WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
  729. WFIFOB(fd, 13) = 0; // is int32 type
  730. WFIFOW(fd, 14) = 0; // count
  731. plen = 16;
  732. /**
  733. * Vessel!
  734. *
  735. * int32 type
  736. * { keyLength(B), key(<keyLength>), index(L), value(L) }
  737. **/
  738. while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) {
  739. Sql_GetData(sql_handle, 0, &data, nullptr);
  740. len = strlen(data)+1;
  741. WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32
  742. plen += 1;
  743. safestrncpy(WFIFOCP(fd,plen), data, len);
  744. plen += static_cast<decltype(plen)>( len );
  745. Sql_GetData(sql_handle, 1, &data, nullptr);
  746. WFIFOL(fd, plen) = (uint32)atol(data);
  747. plen += 4;
  748. Sql_GetData(sql_handle, 2, &data, nullptr);
  749. WFIFOQ(fd, plen) = strtoll(data,nullptr,10);
  750. plen += 8;
  751. WFIFOW(fd, 14) += 1;
  752. if( plen > 60000 ) {
  753. WFIFOW(fd, 2) = plen;
  754. WFIFOSET(fd, plen);
  755. // prepare follow up
  756. WFIFOHEAD(fd, 60000 + 300);
  757. WFIFOW(fd, 0) = 0x2726;
  758. // 0x2 = length, set prior to being sent
  759. WFIFOL(fd, 4) = account_id;
  760. WFIFOL(fd, 8) = char_id;
  761. WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type)
  762. WFIFOB(fd, 13) = 0; // is int32 type
  763. WFIFOW(fd, 14) = 0; // count
  764. plen = 16;
  765. }
  766. }
  767. WFIFOB(fd, 12) = 1;
  768. WFIFOW(fd, 2) = plen;
  769. WFIFOSET(fd, plen);
  770. Sql_FreeResult(sql_handle);
  771. }
  772. bool account_db_sql_enable_webtoken( AccountDB* self, const uint32 account_id ){
  773. AccountDB_SQL* db = (AccountDB_SQL*)self;
  774. if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token_enabled` = '1' WHERE `account_id` = '%u'", db->account_db, account_id ) ){
  775. Sql_ShowDebug( db->accounts );
  776. return false;
  777. }
  778. return true;
  779. }
  780. /**
  781. * Timered function to disable webtoken for user
  782. * If the user is online, then they must have logged since we started the timer.
  783. * In that case, do nothing. The new authtoken must be valid.
  784. * @param tid: timer id
  785. * @param tick: tick of execution
  786. * @param id: user account id
  787. * @param data: AccountDB // because we don't use singleton???
  788. * @return :0
  789. */
  790. TIMER_FUNC(account_disable_webtoken_timer){
  791. const struct online_login_data* p = login_get_online_user(id);
  792. AccountDB_SQL* db = reinterpret_cast<AccountDB_SQL*>(data);
  793. if (p == nullptr) {
  794. ShowInfo("Web Auth Token for account %d was disabled\n", id);
  795. if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token_enabled` = '0' WHERE `account_id` = '%u'", db->account_db, id ) ){
  796. Sql_ShowDebug( db->accounts );
  797. return 0;
  798. }
  799. }
  800. return 0;
  801. }
  802. bool account_db_sql_disable_webtoken( AccountDB* self, const uint32 account_id ){
  803. AccountDB_SQL* db = (AccountDB_SQL*)self;
  804. add_timer(gettick() + login_config.disable_webtoken_delay, account_disable_webtoken_timer, account_id, reinterpret_cast<intptr_t>(db));
  805. return true;
  806. }
  807. bool account_db_sql_remove_webtokens( AccountDB* self ){
  808. AccountDB_SQL* db = (AccountDB_SQL*)self;
  809. if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token` = NULL, `web_auth_token_enabled` = '0'", db->account_db ) ){
  810. Sql_ShowDebug( db->accounts );
  811. return false;
  812. }
  813. return true;
  814. }