Lemongrass Posted March 8, 2013 Group: Developer Topic Count: 28 Topics Per Day: 0.01 Content Count: 547 Reputation: 270 Joined: 11/08/11 Last Seen: June 10, 2024 Share Posted March 8, 2013 Howdy guys, today I worked on something odd and nerving. The pincode system. I got help from Yommy for some of the packets and the decrypt function. But basically I want someone to review my code and additionally I would need some further information about the system. Required infos: How often does a user on kRO have to change his PIN code? The PIN code system checks for the KSSN on AEGIS, I would say we leave that out or do a birthday check instead. Which would mess up the client translation, because that one says "You cant use your KSSN". How can a user delete his PIN? When is the button for the pincode system displayed in char select? Send state 7 to find it there. Has anybody got an UI translation for that system? How often can a user enter his PIN code wrong and what happens then? Thanks in advance and have fun testing. Index: conf/char_athena.conf =================================================================== --- conf/char_athena.conf (revision 17179) +++ conf/char_athena.conf (working copy) @@ -159,4 +159,16 @@ // 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 +// 0: disabled +// 1: enabled +pincode_enabled: 0 + +// How often does a user have to change his pincode? +// Default: 1209600 (2 weeks) +// 0: never +// X: every X seconds +pincode_changetime: 1209600 + import: conf/import/char_conf.txt Index: sql-files/upgrades/upgrade_svn17179.sql =================================================================== --- sql-files/upgrades/upgrade_svn17179.sql (revision 0) +++ sql-files/upgrades/upgrade_svn17179.sql (working copy) @@ -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 Index: src/char/char.c =================================================================== --- src/char/char.c (revision 17179) +++ src/char/char.c (working copy) @@ -131,6 +131,9 @@ 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; }; int max_connect_user = -1; @@ -141,6 +144,23 @@ 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_WRONG 8 + +int pincode_enabled = PINCODE_OK; // PINCODE_OK = off, PINCODE_ASK = on +int pincode_changetime = 14 * 24 * 60 * 60; // every 14 days + +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_notifylogin( int account_id, char* pin ); +void pincode_decrypt( unsigned long userSeed, 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 +2167,7 @@ break; case 0x2717: // account data - if (RFIFOREST(fd) < 63) + if (RFIFOREST(fd) < 68) return 0; // find the authenticated session with this account id @@ -2165,6 +2185,7 @@ } 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)); 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 +2200,28 @@ // 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_NOTSET ); + }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,68); break; // login-server alive packet @@ -4188,6 +4219,50 @@ } return 0; // avoid processing of followup packets here + // checks the entered pin + case 0x8b8: + pincode_check( fd, sd ); + break; + + // request for security window? + case 0x8c5: + if( RFIFOREST(fd) < 6 ) + return 0; + + if( RFIFOL(fd,2) != sd->account_id ) + break; + + // Obviously we should do something here. Open up pincode dialog? + pincode_sendstate( fd, sd, 0 ); + + 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; + + 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)); @@ -4369,6 +4444,102 @@ } //------------------------------------------------ +//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( strcmp( sd->pincode, pin ) == 0 ){ + pincode_sendstate( fd, sd, PINCODE_OK ); + }else{ + pincode_sendstate( fd, sd, PINCODE_WRONG ); + } +} + +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( strcmp( sd->pincode, oldpin ) != 0 ){ + pincode_sendstate( fd, sd, PINCODE_WRONG ); + return; + } + + strncpy(newpin, (char*)RFIFOP(fd,10), 4+1); + pincode_decrypt(sd->pincode_seed,newpin); + + pincode_notifylogin( 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_notifylogin( 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_notifylogin( 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_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 +4862,10 @@ } } 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, "import") == 0) { char_config_read(w2); } Index: src/char/inter.c =================================================================== --- src/char/inter.c (revision 17179) +++ src/char/inter.c (working copy) @@ -387,7 +387,7 @@ 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); Index: src/login/account.h =================================================================== --- src/login/account.h (revision 17179) +++ src/login/account.h (working copy) @@ -50,6 +50,8 @@ 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) }; Index: src/login/account_sql.c =================================================================== --- src/login/account_sql.c (revision 17179) +++ src/login/account_sql.c (working copy) @@ -522,7 +522,7 @@ // 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 @@ 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 @@ 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 @@ || 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 @@ } 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 @@ || 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); Index: src/login/login.c =================================================================== --- src/login/login.c (revision 17179) +++ src/login/login.c (working copy) @@ -562,6 +562,7 @@ 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,11 @@ group_id = acc.group_id; char_slots = acc.char_slots; safestrncpy(birthdate, acc.birthdate, sizeof(birthdate)); + safestrncpy(pincode, acc.pincode, sizeof(pincode)); + acc.pincode_change = 0; } - WFIFOHEAD(fd,63); + WFIFOHEAD(fd,68); WFIFOW(fd,0) = 0x2717; WFIFOL(fd,2) = account_id; safestrncpy((char*)WFIFOP(fd,6), email, 40); @@ -584,7 +587,8 @@ 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 ); + WFIFOSET(fd,68); } break; @@ -910,6 +914,27 @@ 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; + default: ShowError("parse_fromchar: Unknown packet 0x%x from a char-server! Disconnecting!\n", command); set_eof(fd); @@ -961,6 +986,8 @@ 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; pincode_system.patch 3 Link to comment Share on other sites More sharing options...
Flaid Posted March 8, 2013 Group: Members Topic Count: 20 Topics Per Day: 0.00 Content Count: 398 Reputation: 140 Joined: 01/04/12 Last Seen: February 19, 2022 Share Posted March 8, 2013 How often does a user on kRO have to change his PIN code? - You don't have to change it ever if you don't want. The PIN code system checks for the KSSN on AEGIS, I would say we leave that out or do a birthday check instead. Which would mess up the client translation, because that one says "You cant use your KSSN". - Leaving it out for now is fine, I guess. Other opinions? How can a user delete his PIN? - You cannot delete your PIN, you MUST use it when enabled. When is the button for the pincode system displayed in char select? Send state 7 to find it there. - Right after getting to char select Has anybody got an UI translation for that system? - This are the translations, I can come up with all the buttons - if needed. How often can a user enter his PIN code wrong and what happens then? - 3x, nothing really, you get kicked back to the login screen and have to log back in to char select. 2 Link to comment Share on other sites More sharing options...
Lemongrass Posted March 8, 2013 Group: Developer Topic Count: 28 Topics Per Day: 0.01 Content Count: 547 Reputation: 270 Joined: 11/08/11 Last Seen: June 10, 2024 Author Share Posted March 8, 2013 Thats strange then because there are packets for deleting and a must change packet. The problem with sending state 7 directly when you come to char select is that you dont have to enter the pin if you dont click that button. I think I know a workarround for this. So is anyone against commiting this? Link to comment Share on other sites More sharing options...
Flaid Posted March 8, 2013 Group: Members Topic Count: 20 Topics Per Day: 0.00 Content Count: 398 Reputation: 140 Joined: 01/04/12 Last Seen: February 19, 2022 Share Posted March 8, 2013 That weird, my PIN is set since sometime in 2011, I guess? And never was forced to change it. As you can see there's no button for deleting it either. I didn't have a change testing yet, haven't been home for 2 days and probably won't be for another 1-2 but if everything is working fine, I'd say go ahead and commit if nobody minds. Good job on this by the way. Link to comment Share on other sites More sharing options...
Lemongrass Posted March 8, 2013 Group: Developer Topic Count: 28 Topics Per Day: 0.01 Content Count: 547 Reputation: 270 Joined: 11/08/11 Last Seen: June 10, 2024 Author Share Posted March 8, 2013 Commited with some further changes because of Flaid's information in r17183. 5 Link to comment Share on other sites More sharing options...
Recommended Posts