Browse Source

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
lemongrass3110 12 years ago
parent
commit
0fc28289b5

+ 18 - 0
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

+ 2 - 0
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';

+ 219 - 10
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);
 		}

+ 1 - 1
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);

+ 2 - 0
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)
 };

+ 9 - 3
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);

+ 56 - 2
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;