From 0fc28289b580807f7684281d442ad27b23b63bd4 Mon Sep 17 00:00:00 2001 From: lemongrass3110 Date: Fri, 8 Mar 2013 18:43:15 +0000 Subject: [PATCH] Beta Release of PIN Code system. Thanks to Yommy for his help with the basic packet information and LightFighter for the PIN decrypt function. git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@17183 54d463be-8e91-2dee-dedb-b68131a5f0ec --- conf/char_athena.conf | 18 ++ sql-files/upgrades/upgrade_svn17183.sql | 2 + src/char/char.c | 229 ++++++++++++++++++++++-- src/char/inter.c | 2 +- src/login/account.h | 2 + src/login/account_sql.c | 12 +- src/login/login.c | 58 +++++- 7 files changed, 307 insertions(+), 16 deletions(-) create mode 100644 sql-files/upgrades/upgrade_svn17183.sql diff --git a/conf/char_athena.conf b/conf/char_athena.conf index 88687636e4..a66a897c06 100644 --- a/conf/char_athena.conf +++ b/conf/char_athena.conf @@ -159,4 +159,22 @@ char_del_delay: 86400 // What folder the DB files are in (item_db.txt, etc.) db_path: db +// Pincode system +// A window is opened before you can select your character and you will have to enter a pincode by using only your mouse +// NOTE: Requires client 2011-03-09aragexeRE or newer. +// 0: disabled +// 1: enabled +pincode_enabled: 1 + +// How often does a user have to change his pincode? +// Default: 0 +// 0: never +// X: every X seconds +pincode_changetime: 0 + +// How often can a user enter the wrong password? +// Default: 3 +// NOTE: The maximum on clientside is 3 +pincode_maxtry: 3 + import: conf/import/char_conf.txt diff --git a/sql-files/upgrades/upgrade_svn17183.sql b/sql-files/upgrades/upgrade_svn17183.sql new file mode 100644 index 0000000000..608831bbc7 --- /dev/null +++ b/sql-files/upgrades/upgrade_svn17183.sql @@ -0,0 +1,2 @@ +ALTER TABLE `login` ADD COLUMN `pincode` varchar(4) NOT NULL DEFAULT ''; +ALTER TABLE `login` ADD COLUMN `pincode_change` int(11) unsigned NOT NULL DEFAULT '0'; \ No newline at end of file diff --git a/src/char/char.c b/src/char/char.c index 9e7ac40606..9c1f75ddc7 100644 --- a/src/char/char.c +++ b/src/char/char.c @@ -131,6 +131,10 @@ struct char_session_data { uint8 clienttype; char new_name[NAME_LENGTH]; char birthdate[10+1]; // YYYY-MM-DD + char pincode[4+1]; + uint16 pincode_seed; + time_t pincode_change; + uint16 pincode_try; }; int max_connect_user = -1; @@ -141,6 +145,27 @@ int start_weapon = 1201; int start_armor = 2301; int guild_exp_rate = 100; +// Pincode system +#define PINCODE_OK 0 +#define PINCODE_ASK 1 +#define PINCODE_NOTSET 2 +#define PINCODE_EXPIRED 3 +#define PINCODE_UNUSED 7 +#define PINCODE_WRONG 8 + +int pincode_enabled = PINCODE_OK; // PINCODE_OK = off, PINCODE_ASK = on +int pincode_changetime = 0; +int pincode_maxtry = 3; + +void pincode_check( int fd, struct char_session_data* sd ); +void pincode_change( int fd, struct char_session_data* sd ); +void pincode_setnew( int fd, struct char_session_data* sd ); +void pincode_sendstate( int fd, struct char_session_data* sd, uint16 state ); +void pincode_notifyLoginPinUpdate( int account_id, char* pin ); +void pincode_notifyLoginPinError( int account_id ); +void pincode_decrypt( unsigned long userSeed, char* pin ); +int pincode_compare( int fd, struct char_session_data* sd, char* pin ); + //Custom limits for the fame lists. [Skotlex] int fame_list_size_chemist = MAX_FAME_LIST; int fame_list_size_smith = MAX_FAME_LIST; @@ -2147,7 +2172,7 @@ int parse_fromlogin(int fd) { break; case 0x2717: // account data - if (RFIFOREST(fd) < 63) + if (RFIFOREST(fd) < 72) return 0; // find the authenticated session with this account id @@ -2165,6 +2190,8 @@ int parse_fromlogin(int fd) { } else if ( !sd->char_slots )/* no value aka 0 in sql */ sd->char_slots = MAX_CHARS;/* cap to maximum */ safestrncpy(sd->birthdate, (const char*)RFIFOP(fd,52), sizeof(sd->birthdate)); + safestrncpy(sd->pincode, (const char*)RFIFOP(fd,63), sizeof(sd->pincode)); + sd->pincode_change = (time_t)RFIFOL(fd,68); ARR_FIND( 0, ARRAYLENGTH(server), server_id, server[server_id].fd > 0 && server[server_id].map[0] ); // continued from char_auth_ok... if( server_id == ARRAYLENGTH(server) || //server not online, bugreport:2359 @@ -2179,18 +2206,28 @@ int parse_fromlogin(int fd) { // send characters to player mmo_char_send006b(i, sd); #if PACKETVER >= 20110309 - // PIN code system, disabled - WFIFOHEAD(i, 12); - WFIFOW(i, 0) = 0x08B9; - WFIFOW(i, 2) = 0; - WFIFOW(i, 4) = 0; - WFIFOL(i, 6) = sd->account_id; - WFIFOW(i, 10) = 0; - WFIFOSET(i, 12); + if( pincode_enabled ){ + // PIN code system enabled + if( strlen( sd->pincode ) <= 0 ){ + // No PIN code has been set yet + pincode_sendstate( i, sd, PINCODE_UNUSED ); + }else{ + if( !pincode_changetime || sd->pincode_change > time(NULL) ){ + // Ask user for his PIN code + pincode_sendstate( i, sd, PINCODE_ASK ); + }else{ + // User hasnt changed his PIN code too long + pincode_sendstate( i, sd, PINCODE_EXPIRED ); + } + } + }else{ + // PIN code system, disabled + pincode_sendstate( i, sd, PINCODE_OK ); + } #endif } } - RFIFOSKIP(fd,63); + RFIFOSKIP(fd,72); break; // login-server alive packet @@ -4188,6 +4225,58 @@ int parse_char(int fd) } return 0; // avoid processing of followup packets here + // checks the entered pin + case 0x8b8: + if( RFIFOREST(fd) < 10 ) + return 0; + + if( RFIFOL(fd,2) != sd->account_id ) + break; + + pincode_check( fd, sd ); + + RFIFOSKIP(fd,10); + break; + + // request for PIN window + case 0x8c5: + if( RFIFOREST(fd) < 6 ) + return 0; + + if( RFIFOL(fd,2) != sd->account_id ) + break; + + pincode_sendstate( fd, sd, PINCODE_NOTSET ); + + RFIFOSKIP(fd,6); + break; + + // pincode change request + case 0x8be: + if( RFIFOREST(fd) < 14 ) + return 0; + + if( RFIFOL(fd,2) != sd->account_id ) + break; + + pincode_change( fd, sd ); + + RFIFOSKIP(fd,14); + break; + + // activate PIN system and set first PIN + case 0x8ba: + if( RFIFOREST(fd) < 10 ) + return 0; + + if( RFIFOL(fd,2) != sd->account_id ) + break; + + pincode_setnew( fd, sd ); + + RFIFOSKIP(fd,10); + break; + // unknown packet received default: ShowError("parse_char: Received unknown packet "CL_WHITE"0x%x"CL_RESET" from ip '"CL_WHITE"%s"CL_RESET"'! Disconnecting!\n", RFIFOW(fd,0), ip2str(ipl, NULL)); @@ -4368,6 +4457,120 @@ int check_connect_login_server(int tid, unsigned int tick, int id, intptr_t data return 1; } +//------------------------------------------------ +//Pincode system +//------------------------------------------------ +void pincode_check( int fd, struct char_session_data* sd ){ + char pin[5] = "\0\0\0\0"; + strncpy((char*)pin, (char*)RFIFOP(fd, 6), 4+1); + + pincode_decrypt(sd->pincode_seed, pin ); + + if( pincode_compare( fd, sd, pin ) ){ + pincode_sendstate( fd, sd, PINCODE_OK ); + } +} + +int pincode_compare( int fd, struct char_session_data* sd, char* pin ){ + if( strcmp( sd->pincode, pin ) == 0 ){ + sd->pincode_try = 0; + return 1; + }else{ + pincode_sendstate( fd, sd, PINCODE_WRONG ); + + if( pincode_maxtry && ++sd->pincode_try >= pincode_maxtry ){ + pincode_notifyLoginPinError( sd->account_id ); + } + + return 0; + } +} + +void pincode_change( int fd, struct char_session_data* sd ){ + char oldpin[5] = "\0\0\0\0"; + char newpin[5] = "\0\0\0\0"; + + strncpy(oldpin, (char*)RFIFOP(fd,6), 4+1); + pincode_decrypt(sd->pincode_seed,oldpin); + + if( !pincode_compare( fd, sd, oldpin ) ) + return; + + strncpy(newpin, (char*)RFIFOP(fd,10), 4+1); + pincode_decrypt(sd->pincode_seed,newpin); + + pincode_notifyLoginPinUpdate( sd->account_id, newpin ); + + pincode_sendstate( fd, sd, PINCODE_OK ); +} + +void pincode_setnew( int fd, struct char_session_data* sd ){ + char newpin[5] = "\0\0\0\0"; + + strncpy(newpin, (char*)RFIFOP(fd,6), 4+1); + pincode_decrypt(sd->pincode_seed,newpin); + + pincode_notifyLoginPinUpdate( sd->account_id, newpin ); + + pincode_sendstate( fd, sd, PINCODE_OK ); +} + +// 0 = disabled / pin is correct +// 1 = ask for pin - client sends 0x8b8 +// 2 = create new pin - client sends 0x8ba +// 3 = pin must be changed - client 0x8be +// 4 = create new pin ?? - client sends 0x8ba +// 5 = client shows msgstr(1896) +// 6 = client shows msgstr(1897) Unable to use your KSSN number +// 7 = char select window shows a button - client sends 0x8c5 +// 8 = pincode was incorrect +void pincode_sendstate( int fd, struct char_session_data* sd, uint16 state ){ + WFIFOHEAD(fd, 12); + WFIFOW(fd, 0) = 0x8b9; + WFIFOL(fd, 2) = sd->pincode_seed = rand() % 0xFFFF; + WFIFOL(fd, 6) = sd->account_id; + WFIFOW(fd,10) = state; + WFIFOSET(fd,12); +} + +void pincode_notifyLoginPinUpdate( int account_id, char* pin ){ + WFIFOHEAD(login_fd,15); + WFIFOW(login_fd,0) = 0x2738; + WFIFOL(login_fd,2) = account_id; + strncpy( (char*)WFIFOP(login_fd,6), pin, 5 ); + WFIFOL(login_fd,11) = pincode_changetime; + WFIFOSET(login_fd,15); +} + +void pincode_notifyLoginPinError( int account_id ){ + WFIFOHEAD(login_fd,6); + WFIFOW(login_fd,0) = 0x2739; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); +} + +void pincode_decrypt( unsigned long userSeed, char* pin ){ + int i, pos; + char tab[10] = {0,1,2,3,4,5,6,7,8,9}; + unsigned long multiplier = 0x3498, baseSeed = 0x881234; + + for( i = 1; i < 10; i++ ){ + userSeed = baseSeed + userSeed * multiplier; + pos = userSeed % (i + 1); + if( i != pos ){ + tab[i] ^= tab[pos]; + tab[pos] ^= tab[i]; + tab[i] ^= tab[pos]; + } + } + + for( i = 0; i < 4; i++ ){ + pin[i] = tab[pin[i]- '0']; + } + + sprintf(pin, "%d%d%d%d", pin[0], pin[1], pin[2], pin[3]); +} + //------------------------------------------------ //Invoked 15 seconds after mapif_disconnectplayer in case the map server doesn't //replies/disconnect the player we tried to kick. [Skotlex] @@ -4691,6 +4894,12 @@ int char_config_read(const char* cfgName) } } else if (strcmpi(w1, "guild_exp_rate") == 0) { guild_exp_rate = atoi(w2); + } else if (strcmpi(w1, "pincode_enabled") == 0) { + pincode_enabled = atoi(w2); + } else if (strcmpi(w1, "pincode_changetime") == 0) { + pincode_changetime = atoi(w2); + } else if (strcmpi(w1, "pincode_maxtry") == 0) { + pincode_maxtry = atoi(w2); } else if (strcmpi(w1, "import") == 0) { char_config_read(w2); } diff --git a/src/char/inter.c b/src/char/inter.c index 617dcc222c..4f2eb8c456 100644 --- a/src/char/inter.c +++ b/src/char/inter.c @@ -387,7 +387,7 @@ void mapif_parse_accinfo(int fd) { account_id = atoi(query); if (account_id < START_ACCOUNT_NUM) { // is string - if ( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`name`,`class`,`base_level`,`job_level`,`online` FROM `char` WHERE `name` LIKE '%s' LIMIT 10", query_esq) + if ( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`,`name`,`class`,`base_level`,`job_level`,`online` FROM `%s` WHERE `name` LIKE '%s' LIMIT 10", char_db, query_esq) || Sql_NumRows(sql_handle) == 0 ) { if( Sql_NumRows(sql_handle) == 0 ) { inter_to_fd(fd, u_fd, aid, "No matches were found for your criteria, '%s'",query); diff --git a/src/login/account.h b/src/login/account.h index 8641f6302b..8ba486e2c3 100644 --- a/src/login/account.h +++ b/src/login/account.h @@ -50,6 +50,8 @@ struct mmo_account char lastlogin[24]; // date+time of last successful login char last_ip[16]; // save of last IP of connection char birthdate[10+1]; // assigned birth date (format: YYYY-MM-DD, default: 0000-00-00) + char pincode[4+1]; // pincode system + time_t pincode_change; // (timestamp): last time of pincode change int account_reg2_num; struct global_reg account_reg2[ACCOUNT_REG2_NUM]; // account script variables (stored on login server) }; diff --git a/src/login/account_sql.c b/src/login/account_sql.c index 7c24555904..e8d2dd42d8 100644 --- a/src/login/account_sql.c +++ b/src/login/account_sql.c @@ -522,7 +522,7 @@ static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, int acc // 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` FROM `%s` WHERE `account_id` = %d", + "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); @@ -549,6 +549,8 @@ static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, int acc 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, sizeof(acc->birthdate)); Sql_GetData(sql_handle, 13, &data, NULL); acc->char_slots = 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 = atol(data); Sql_FreeResult(sql_handle); @@ -597,7 +599,7 @@ static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, boo if( is_new ) {// insert into account table if( SQL_SUCCESS != SqlStmt_Prepare(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`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "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 != 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)) @@ -613,6 +615,8 @@ static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, boo || SQL_SUCCESS != SqlStmt_BindParam(stmt, 11, SQLDT_STRING, (void*)&acc->last_ip, strlen(acc->last_ip)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 12, SQLDT_STRING, (void*)&acc->birthdate, strlen(acc->birthdate)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 13, SQLDT_UCHAR, (void*)&acc->char_slots, sizeof(acc->char_slots)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 14, SQLDT_STRING, (void*)&acc->pincode, sizeof(acc->pincode)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 15, SQLDT_LONG, (void*)&acc->pincode_change, sizeof(acc->pincode_change)) || SQL_SUCCESS != SqlStmt_Execute(stmt) ) { SqlStmt_ShowDebug(stmt); @@ -621,7 +625,7 @@ static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, boo } else {// update account table - if( SQL_SUCCESS != SqlStmt_Prepare(stmt, "UPDATE `%s` SET `userid`=?,`user_pass`=?,`sex`=?,`email`=?,`group_id`=?,`state`=?,`unban_time`=?,`expiration_time`=?,`logincount`=?,`lastlogin`=?,`last_ip`=?,`birthdate`=?,`character_slots`=? WHERE `account_id` = '%d'", db->account_db, acc->account_id) + if( SQL_SUCCESS != SqlStmt_Prepare(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 != 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)) @@ -635,6 +639,8 @@ static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, boo || SQL_SUCCESS != SqlStmt_BindParam(stmt, 10, SQLDT_STRING, (void*)&acc->last_ip, strlen(acc->last_ip)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 11, SQLDT_STRING, (void*)&acc->birthdate, strlen(acc->birthdate)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 12, SQLDT_UCHAR, (void*)&acc->char_slots, sizeof(acc->char_slots)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 13, SQLDT_STRING, (void*)&acc->pincode, strlen(acc->pincode)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 14, SQLDT_LONG, (void*)&acc->pincode_change, sizeof(acc->pincode_change)) || SQL_SUCCESS != SqlStmt_Execute(stmt) ) { SqlStmt_ShowDebug(stmt); diff --git a/src/login/login.c b/src/login/login.c index f7995066aa..f73698d8ee 100644 --- a/src/login/login.c +++ b/src/login/login.c @@ -562,6 +562,7 @@ int parse_fromchar(int fd) uint8 char_slots = 0; int group_id = 0; char birthdate[10+1] = ""; + char pincode[4+1] = ""; int account_id = RFIFOL(fd,2); RFIFOSKIP(fd,6); @@ -574,9 +575,10 @@ int parse_fromchar(int fd) group_id = acc.group_id; char_slots = acc.char_slots; safestrncpy(birthdate, acc.birthdate, sizeof(birthdate)); + safestrncpy(pincode, acc.pincode, sizeof(pincode)); } - WFIFOHEAD(fd,63); + WFIFOHEAD(fd,72); WFIFOW(fd,0) = 0x2717; WFIFOL(fd,2) = account_id; safestrncpy((char*)WFIFOP(fd,6), email, 40); @@ -584,7 +586,9 @@ int parse_fromchar(int fd) WFIFOB(fd,50) = (unsigned char)group_id; WFIFOB(fd,51) = char_slots; safestrncpy((char*)WFIFOP(fd,52), birthdate, 10+1); - WFIFOSET(fd,63); + safestrncpy((char*)WFIFOP(fd,63), pincode, 4+1 ); + WFIFOL(fd,68) = (uint32)acc.pincode_change; + WFIFOSET(fd,72); } break; @@ -910,6 +914,54 @@ int parse_fromchar(int fd) RFIFOSKIP(fd,2); break; + case 0x2738: //Change PIN Code for a account + if( RFIFOREST(fd) < 15 ) + return 0; + + { + struct mmo_account acc; + + if( accounts->load_num(accounts, &acc, RFIFOL(fd,2) ) ) + { + strncpy( acc.pincode, (char*)RFIFOP(fd,6), 5 ); + acc.pincode_change = RFIFOL(fd,11); + if( acc.pincode_change > 0 ){ + acc.pincode_change += time( NULL ); + } + accounts->save(accounts, &acc); + } + + + } + RFIFOSKIP(fd,15); + break; + + case 0x2739: // PIN Code was entered wrong too often + if( RFIFOREST(fd) < 6 ) + return 0; + + { + + struct mmo_account acc; + + if( accounts->load_num(accounts, &acc, RFIFOL(fd,2) ) ) + { + struct online_login_data* ld; + + ld = (struct online_login_data*)idb_get(online_db,acc.account_id); + + if( ld == NULL ) + return 0; + + login_log( host2ip(acc.last_ip), acc.userid, 100, "PIN Code check failed" ); + } + + remove_online_user(acc.account_id); + } + + RFIFOSKIP(fd,6); + break; + default: ShowError("parse_fromchar: Unknown packet 0x%x from a char-server! Disconnecting!\n", command); set_eof(fd); @@ -961,6 +1013,8 @@ int mmo_auth_new(const char* userid, const char* pass, const char sex, const cha safestrncpy(acc.lastlogin, "0000-00-00 00:00:00", sizeof(acc.lastlogin)); safestrncpy(acc.last_ip, last_ip, sizeof(acc.last_ip)); safestrncpy(acc.birthdate, "0000-00-00", sizeof(acc.birthdate)); + safestrncpy(acc.pincode, "", sizeof(acc.pincode)); + acc.pincode_change = 0; acc.char_slots = 0;