/// Make the SRP6 calculation from hash in dB void AuthSocket::_SetVSFields(const std::string& rI) { s.SetRand(s_BYTE_SIZE * 8); BigNumber I; I.SetHexStr(rI.c_str()); // In case of leading zeros in the rI hash, restore them uint8 mDigest[SHA_DIGEST_LENGTH]; memset(mDigest, 0, SHA_DIGEST_LENGTH); if (I.GetNumBytes() <= SHA_DIGEST_LENGTH) memcpy(mDigest, I.AsByteArray(), I.GetNumBytes()); std::reverse(mDigest, mDigest + SHA_DIGEST_LENGTH); Sha1Hash sha; sha.UpdateData(s.AsByteArray(), s.GetNumBytes()); sha.UpdateData(mDigest, SHA_DIGEST_LENGTH); sha.Finalize(); BigNumber x; x.SetBinary(sha.GetDigest(), sha.GetLength()); v = g.ModExp(x, N); // No SQL injection (username escaped) const char* v_hex, *s_hex; v_hex = v.AsHexStr(); s_hex = s.AsHexStr(); LoginDatabase.PExecute("UPDATE account SET v = '%s', s = '%s' WHERE username = '******'", v_hex, s_hex, _safelogin.c_str()); OPENSSL_free((void*)v_hex); OPENSSL_free((void*)s_hex); }
void SHA1Hash::UpdateBigNumbers(BigNumber *bn0, ...) { va_list v; BigNumber *bn; va_start(v, bn0); bn = bn0; while (bn) { UpdateData(bn->AsByteArray(), bn->GetNumBytes()); bn = va_arg(v, BigNumber *); } va_end(v); }
// Make the SRP6 calculation from hash in dB void AuthSocket::_SetVSFields(const std::string& rI) { s.SetRand(s_BYTE_SIZE * 8); BigNumber I; I.SetHexStr(rI.c_str()); // In case of leading zeros in the rI hash, restore them uint8 mDigest[SHA_DIGEST_LENGTH]; memset(mDigest, 0, SHA_DIGEST_LENGTH); if (I.GetNumBytes() <= SHA_DIGEST_LENGTH) { memcpy(mDigest, I.AsByteArray(), I.GetNumBytes()); } std::reverse(mDigest, mDigest + SHA_DIGEST_LENGTH); SHA1Hash sha; sha.UpdateData(s.AsByteArray(), s.GetNumBytes()); sha.UpdateData(mDigest, SHA_DIGEST_LENGTH); sha.Finalize(); BigNumber x; x.SetBinary(sha.GetDigest(), sha.GetLength()); v = g.ModExp(x, N); // No SQL injection (username escaped) const char *v_hex, *s_hex; v_hex = v.AsHexStr(); s_hex = s.AsHexStr(); PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_VS); stmt->setString(0, v_hex); stmt->setString(1, s_hex); stmt->setString(2, _login); LoginDatabase.Execute(stmt); OPENSSL_free((void*)v_hex); OPENSSL_free((void*)s_hex); }
/// Make the SRP6 calculation from hash in dB void AuthSocket::_SetVSFields(std::string rI) { BigNumber I; I.SetHexStr(rI.c_str()); //In case of leading zeroes in the rI hash, restore them uint8 mDigest[SHA_DIGEST_LENGTH]; memset(mDigest,0,SHA_DIGEST_LENGTH); if (I.GetNumBytes() <= SHA_DIGEST_LENGTH) memcpy(mDigest,I.AsByteArray(),I.GetNumBytes()); std::reverse(mDigest,mDigest+SHA_DIGEST_LENGTH); Sha1Hash sha; sha.UpdateData(s.AsByteArray(), s.GetNumBytes()); sha.UpdateData(mDigest, SHA_DIGEST_LENGTH); sha.Finalize(); BigNumber x; x.SetBinary(sha.GetDigest(), sha.GetLength()); v = g.ModExp(x, N); // No SQL injection (username escaped) dbRealmServer.PExecute("UPDATE `account` SET `v` = '%s', `s` = '%s' WHERE UPPER(`username`)= UPPER('%s')",v.AsHexStr(),s.AsHexStr(), _safelogin.c_str() ); }
void AuthSession::LogonChallengeCallback(PreparedQueryResult result) { ByteBuffer pkt; pkt << uint8(AUTH_LOGON_CHALLENGE); pkt << uint8(0x00); if (!result) { pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); SendPacket(pkt); return; } Field* fields = result->Fetch(); _accountInfo.LoadResult(fields); std::string ipAddress = GetRemoteIpAddress().to_string(); uint16 port = GetRemotePort(); // If the IP is 'locked', check that the player comes indeed from the correct IP address if (_accountInfo.IsLockedToIP) { TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to IP - '%s' is logging in from '%s'", _accountInfo.Login.c_str(), _accountInfo.LastIP.c_str(), ipAddress.c_str()); if (_accountInfo.LastIP != ipAddress) { pkt << uint8(WOW_FAIL_LOCKED_ENFORCED); SendPacket(pkt); return; } } else { TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to ip", _accountInfo.Login.c_str()); if (_accountInfo.LockCountry.empty() || _accountInfo.LockCountry == "00") TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to country", _accountInfo.Login.c_str()); else if (!_accountInfo.LockCountry.empty() && !_ipCountry.empty()) { TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to country: '%s' Player country is '%s'", _accountInfo.Login.c_str(), _accountInfo.LockCountry.c_str(), _ipCountry.c_str()); if (_ipCountry != _accountInfo.LockCountry) { pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK); SendPacket(pkt); return; } } } // If the account is banned, reject the logon attempt if (_accountInfo.IsBanned) { if (_accountInfo.IsPermanenetlyBanned) { pkt << uint8(WOW_FAIL_BANNED); SendPacket(pkt); TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str()); return; } else { pkt << uint8(WOW_FAIL_SUSPENDED); SendPacket(pkt); TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str()); return; } } // Get the password from the account table, upper it, and make the SRP6 calculation std::string rI = fields[10].GetString(); // Don't calculate (v, s) if there are already some in the database std::string databaseV = fields[11].GetString(); std::string databaseS = fields[12].GetString(); TC_LOG_DEBUG("network", "database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); // multiply with 2 since bytes are stored as hexstring if (databaseV.size() != size_t(BufferSizes::SRP_6_V) * 2 || databaseS.size() != size_t(BufferSizes::SRP_6_S) * 2) SetVSFields(rI); else { s.SetHexStr(databaseS.c_str()); v.SetHexStr(databaseV.c_str()); } b.SetRand(19 * 8); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16 * 8); // Fill the response packet with the result if (AuthHelper::IsAcceptedClientBuild(_build)) pkt << uint8(WOW_SUCCESS); else pkt << uint8(WOW_FAIL_VERSION_INVALID); // B may be calculated < 32B so we force minimal length to 32B pkt.append(B.AsByteArray(32).get(), 32); // 32 bytes pkt << uint8(1); pkt.append(g.AsByteArray(1).get(), 1); pkt << uint8(32); pkt.append(N.AsByteArray(32).get(), 32); pkt.append(s.AsByteArray(int32(BufferSizes::SRP_6_S)).get(), size_t(BufferSizes::SRP_6_S)); // 32 bytes pkt.append(unk3.AsByteArray(16).get(), 16); uint8 securityFlags = 0; // Check if token is used _tokenKey = fields[9].GetString(); if (!_tokenKey.empty()) securityFlags = 4; pkt << uint8(securityFlags); // security flags (0x0...0x04) if (securityFlags & 0x01) // PIN input { pkt << uint32(0); pkt << uint64(0) << uint64(0); // 16 bytes hash? } if (securityFlags & 0x02) // Matrix input { pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint64(0); } if (securityFlags & 0x04) // Security token input pkt << uint8(1); TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s is using '%s' locale (%u)", ipAddress.c_str(), port, _accountInfo.Login.c_str(), _localizationName.c_str(), GetLocaleByName(_localizationName)); SendPacket(pkt); }
void AccountMgr::AddAccount(Field* field) { Account * acct = new Account; Sha1Hash hash; string Username = field[1].GetString(); string Password = field[2].GetString(); //string EncryptedPassword = field[3].GetString(); string GMFlags = field[3].GetString(); acct->AccountId = field[0].GetUInt32(); acct->AccountFlags = field[4].GetUInt8(); acct->Banned = field[5].GetUInt32(); if ( (uint32)UNIXTIME > acct->Banned && acct->Banned != 0 && acct->Banned != 1) //1 = perm ban? { //Accounts should be unbanned once the date is past their set expiry date. acct->Banned = 0; //me go boom :( //printf("Account %s's ban has expired.\n",acct->UsernamePtr->c_str()); sLogonSQL->Execute("UPDATE accounts SET banned = 0 WHERE acct=%u",acct->AccountId); } acct->SetGMFlags(GMFlags.c_str()); acct->Locale[0] = 'e'; acct->Locale[1] = 'n'; acct->Locale[2] = 'U'; acct->Locale[3] = 'S'; if(strcmp(field[6].GetString(), "enUS")) { // non-standard language forced memcpy(acct->Locale, field[6].GetString(), 4); acct->forcedLocale = true; } else acct->forcedLocale = false; acct->Muted = field[7].GetUInt32(); if ( (uint32)UNIXTIME > acct->Muted && acct->Muted != 0 && acct->Muted != 1) //1 = perm ban? { //Accounts should be unbanned once the date is past their set expiry date. acct->Muted= 0; DEBUG_LOG("AccountMgr","Account %s's mute has expired.", Username.c_str()); sLogonSQL->Execute("UPDATE accounts SET muted = 0 WHERE acct=%u",acct->AccountId); } // Convert username/password to uppercase. this is needed ;) HEARTHSTONE_TOUPPER(Username); HEARTHSTONE_TOUPPER(Password); if( m_encryptedPasswords ) { // prefer encrypted passwords over nonencrypted BigNumber bn; bn.SetHexStr( Password.c_str() ); if( bn.GetNumBytes() != 20 ) { // Someone probably has non-encrypted passwords in a server that's set to encrypted pws. hash.UpdateData((Username + ":" + Password)); hash.Finalize(); memcpy(acct->SrpHash, hash.GetDigest(), 20); // Make sure this doesn't happen again. BigNumber cnSave; cnSave.SetBinary( acct->SrpHash, 20); string hash = cnSave.AsHexStr(); DEBUG_LOG("AccountMgr", "Found account %s [%u] with invalid password format. Converting to encrypted password.", Username.c_str(), acct->AccountId); sLogonSQL->Execute("UPDATE accounts SET password = SHA1(CONCAT(UPPER(login), ':', UPPER(password))) WHERE acct = %u", acct->AccountId); } else { if ( Password.size() == 40 ) { if( bn.GetNumBytes() < 20 ) { memcpy(acct->SrpHash, bn.AsByteArray(), bn.GetNumBytes()); for (int n=bn.GetNumBytes(); n<=19; n++) acct->SrpHash[n] = (uint8)0; reverse_array(acct->SrpHash, 20); } else { memcpy(acct->SrpHash, bn.AsByteArray(), 20); reverse_array(acct->SrpHash, 20); } } } } else { // Prehash the I value. hash.UpdateData((Username + ":" + Password)); hash.Finalize(); memcpy(acct->SrpHash, hash.GetDigest(), 20); } AccountDatabase[Username] = acct; }
void AuthSocket::HandleChallenge() { // No header if(GetReadBufferSize() < 4) return; // Check the rest of the packet is complete. uint8 * ReceiveBuffer = this->GetReadBuffer(0); uint16 full_size = *(uint16*)&ReceiveBuffer[2]; sLog.outDetail("[AuthChallenge] got header, body is 0x%02X bytes", full_size); if(GetReadBufferSize() < uint32(full_size+4)) return; // Copy the data into our cached challenge structure if(full_size > sizeof(sAuthLogonChallenge_C)) { Disconnect(); return; } sLog.outDebug("[AuthChallenge] got full packet."); memcpy(&m_challenge, ReceiveBuffer, full_size + 4); RemoveReadBufferBytes(full_size + 4, true); // Check client build. if(m_challenge.build > LogonServer::getSingleton().max_build || m_challenge.build < LogonServer::getSingleton().min_build) { SendChallengeError(CE_WRONG_BUILD_NUMBER); return; } // Check for a possible IP ban on this client. BAN_STATUS ipb = IPBanner::getSingleton().CalculateBanStatus(GetRemoteAddress()); switch(ipb) { case BAN_STATUS_PERMANANT_BAN: SendChallengeError(CE_ACCOUNT_CLOSED); return; case BAN_STATUS_TIME_LEFT_ON_BAN: SendChallengeError(CE_ACCOUNT_FREEZED); return; } // Null-terminate the account string m_challenge.I[m_challenge.I_len] = 0; // Look up the account information string AccountName = (char*)&m_challenge.I; sLog.outDebug("[AuthChallenge] Account Name: \"%s\"", AccountName.c_str()); m_account = AccountMgr::getSingleton().GetAccount(AccountName); if(m_account == 0) { sLog.outDebug("[AuthChallenge] Invalid account."); // Non-existant account SendChallengeError(CE_NO_ACCOUNT); return; } sLog.outDebug("[AuthChallenge] Account banned state = %u", m_account->Banned); // Check that the account isn't banned. if(m_account->Banned == 1) { SendChallengeError(CE_ACCOUNT_CLOSED); return; } else if(m_account->Banned > 0) { SendChallengeError(CE_ACCOUNT_FREEZED); return; } Sha1Hash sha; //uint32 tc = s.GetNumBytes(); sha.UpdateData( s.AsByteArray(), 32 ); sha.UpdateData( m_account->SrpHash, 20 ); sha.Finalize(); BigNumber x; x.SetBinary( sha.GetDigest(), sha.GetLength() ); v = g.ModExp(x, N); b.SetRand(152); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk; unk.SetRand(128); uint8 response[200]; uint32 c = 0; response[c] = 0; c += 1; response[c] = 0; c += 1; response[c] = CE_SUCCESS; c += 1; memcpy(&response[c], B.AsByteArray(), 32); c += 32; response[c] = 1; c += 1; response[c] = g.AsByteArray()[0]; c += 1; response[c] = 32; c += 1; memcpy(&response[c], N.AsByteArray(), 32); c += 32; memcpy(&response[c], s.AsByteArray(), s.GetNumBytes()); c += s.GetNumBytes(); memcpy(&response[c], unk.AsByteArray(), 16); c += 16; response[c] = 0; c += 1; Send(response, c); }
// Logon Challenge command handler bool AuthSocket::_HandleLogonChallenge() { sLog->outDebug(LOG_FILTER_AUTHSERVER, "Entering _HandleLogonChallenge"); if (socket().recv_len() < sizeof(sAuthLogonChallenge_C)) return false; // Read the first 4 bytes (header) to get the length of the remaining of the packet std::vector<uint8> buf; buf.resize(4); socket().recv((char *)&buf[0], 4); #if TRINITY_ENDIAN == TRINITY_BIGENDIAN EndianConvert(*((uint16*)(buf[0]))); #endif uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size; sLog->outDebug(LOG_FILTER_AUTHSERVER, "[AuthChallenge] got header, body is %#04x bytes", remaining); if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (socket().recv_len() < remaining)) return false; //No big fear of memory outage (size is int16, i.e. < 65536) buf.resize(remaining + buf.size() + 1); buf[buf.size() - 1] = 0; sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0]; // Read the remaining of the packet socket().recv((char *)&buf[4], remaining); sLog->outDebug(LOG_FILTER_AUTHSERVER, "[AuthChallenge] got full packet, %#04x bytes", ch->size); sLog->outDebug(LOG_FILTER_AUTHSERVER, "[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I); // BigEndian code, nop in little endian case // size already converted #if TRINITY_ENDIAN == TRINITY_BIGENDIAN EndianConvert(*((uint32*)(&ch->gamename[0]))); EndianConvert(ch->build); EndianConvert(*((uint32*)(&ch->platform[0]))); EndianConvert(*((uint32*)(&ch->os[0]))); EndianConvert(*((uint32*)(&ch->country[0]))); EndianConvert(ch->timezone_bias); EndianConvert(ch->ip); #endif ByteBuffer pkt; _login = (const char*)ch->I; _build = ch->build; _os = (const char*)ch->os; if (_os.size() > 4) return false; // Restore string order as its byte order is reversed std::reverse(_os.begin(), _os.end()); pkt << uint8(AUTH_LOGON_CHALLENGE); pkt << uint8(0x00); // Verify that this IP is not in the ip_banned table LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); const std::string& ip_address = socket().getRemoteAddress(); PreparedStatement *stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_BANNED); stmt->setString(0, ip_address); PreparedQueryResult result = LoginDatabase.Query(stmt); if (result) { pkt << (uint8)WOW_FAIL_BANNED; sLog->outDebug(LOG_FILTER_AUTHSERVER, "'%s:%d' [AuthChallenge] Banned ip tries to login!",socket().getRemoteAddress().c_str(), socket().getRemotePort()); } else { // Get the account details from the account table // No SQL injection (prepared statement) stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE); stmt->setString(0, _login); PreparedQueryResult res2 = LoginDatabase.Query(stmt); if (res2) { Field* fields = res2->Fetch(); // If the IP is 'locked', check that the player comes indeed from the correct IP address bool locked = false; if (fields[2].GetUInt8() == 1) // if ip is locked { sLog->outDebug(LOG_FILTER_AUTHSERVER, "[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), fields[3].GetCString()); sLog->outDebug(LOG_FILTER_AUTHSERVER, "[AuthChallenge] Player address is '%s'", ip_address.c_str()); if (strcmp(fields[3].GetCString(), ip_address.c_str())) { sLog->outDebug(LOG_FILTER_AUTHSERVER, "[AuthChallenge] Account IP differs"); pkt << uint8(WOW_FAIL_SUSPENDED); locked = true; } else sLog->outDebug(LOG_FILTER_AUTHSERVER, "[AuthChallenge] Account IP matches"); } else sLog->outDebug(LOG_FILTER_AUTHSERVER, "[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); if (!locked) { //set expired bans to inactive LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS)); LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_PREMIUM)); // If the account is banned, reject the logon attempt stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BANNED); stmt->setUInt32(0, fields[1].GetUInt32()); PreparedQueryResult banresult = LoginDatabase.Query(stmt); if (banresult) { if ((*banresult)[0].GetUInt32() == (*banresult)[1].GetUInt32()) { pkt << (uint8)WOW_FAIL_BANNED; sLog->outDebug(LOG_FILTER_AUTHSERVER, "'%s:%d' [AuthChallenge] Banned account %s tried to login!", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str ()); } else { pkt << (uint8)WOW_FAIL_SUSPENDED; sLog->outDebug(LOG_FILTER_AUTHSERVER, "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str ()); } } else { // Get the password from the account table, upper it, and make the SRP6 calculation std::string rI = fields[0].GetString(); // Don't calculate (v, s) if there are already some in the database std::string databaseV = fields[5].GetString(); std::string databaseS = fields[6].GetString(); sLog->outDebug(LOG_FILTER_NETWORKIO, "database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); // multiply with 2 since bytes are stored as hexstring if (databaseV.size() != s_BYTE_SIZE * 2 || databaseS.size() != s_BYTE_SIZE * 2) _SetVSFields(rI); else { s.SetHexStr(databaseS.c_str()); v.SetHexStr(databaseV.c_str()); } b.SetRand(19 * 8); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16 * 8); // Fill the response packet with the result // If the client has no valid version if (!AuthHelper::IsAcceptedClientBuild(_build)) pkt << uint8(WOW_FAIL_VERSION_INVALID); else pkt << uint8(WOW_SUCCESS); // B may be calculated < 32B so we force minimal length to 32B pkt.append(B.AsByteArray(32), 32); // 32 bytes pkt << uint8(1); pkt.append(g.AsByteArray(), 1); pkt << uint8(32); pkt.append(N.AsByteArray(32), 32); pkt.append(s.AsByteArray(), s.GetNumBytes()); // 32 bytes pkt.append(unk3.AsByteArray(16), 16); uint8 securityFlags = 0; // Check if token is used _tokenKey = fields[7].GetString(); if (!_tokenKey.empty()) securityFlags = 4; pkt << uint8(securityFlags); // security flags (0x0...0x04) if (securityFlags & 0x01) // PIN input { pkt << uint32(0); pkt << uint64(0) << uint64(0); // 16 bytes hash? } if (securityFlags & 0x02) // Matrix input { pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint64(0); } if (securityFlags & 0x04) // Security token input pkt << uint8(1); uint8 secLevel = fields[4].GetUInt8(); _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; _localizationName.resize(4); for (int i = 0; i < 4; ++i) _localizationName[i] = ch->country[4-i-1]; sLog->outDebug(LOG_FILTER_AUTHSERVER, "'%s:%d' [AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str (), ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName) ); } } } else //no account pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); } socket().send((char const*)pkt.contents(), pkt.size()); return true; }
void AuthSocket::HandleChallenge() { // No header if(readBuffer.GetContiguiousBytes() < 4) { LOG_ERROR("[AuthChallenge] Packet has no header. Refusing to handle."); return; } // Check the rest of the packet is complete. uint8* ReceiveBuffer = (uint8*)readBuffer.GetBufferStart(); uint16 full_size = *(uint16*)&ReceiveBuffer[2]; LOG_DETAIL("[AuthChallenge] got header, body is %u bytes", full_size); if(readBuffer.GetSize() < uint32(full_size + 4)) { LOG_ERROR("[AuthChallenge] Packet is smaller than expected, refusing to handle"); return; } // Copy the data into our cached challenge structure if(full_size > sizeof(sAuthLogonChallenge_C)) { LOG_ERROR("[AuthChallenge] Packet is larger than expected, refusing to handle!"); Disconnect(); return; } LOG_DEBUG("[AuthChallenge] got a complete packet."); //memcpy(&m_challenge, ReceiveBuffer, full_size + 4); //RemoveReadBufferBytes(full_size + 4, true); readBuffer.Read(&m_challenge, full_size + 4); // Check client build. uint16 build = m_challenge.build; // Check client build. if(build > LogonServer::getSingleton().max_build) { // wtf? LOG_DETAIL("[AuthChallenge] Client %s has wrong version. More up to date than server. Server: %u, Client: %u", GetRemoteIP().c_str(), LogonServer::getSingleton().max_build, m_challenge.build); SendChallengeError(CE_WRONG_BUILD_NUMBER); return; } if(build < LogonServer::getSingleton().min_build) { // can we patch? char flippedloc[5] = {0, 0, 0, 0, 0}; flippedloc[0] = m_challenge.country[3]; flippedloc[1] = m_challenge.country[2]; flippedloc[2] = m_challenge.country[1]; flippedloc[3] = m_challenge.country[0]; m_patch = PatchMgr::getSingleton().FindPatchForClient(build, flippedloc); if(m_patch == NULL) { // could not find a valid patch LOG_DETAIL("[AuthChallenge] Client %s has wrong version. More out of date than server. Server: %u, Client: %u", GetRemoteIP().c_str(), LogonServer::getSingleton().min_build, m_challenge.build); SendChallengeError(CE_WRONG_BUILD_NUMBER); return; } Log.Debug("Patch", "Selected patch %u%s for client.", m_patch->Version, m_patch->Locality); uint8 response[119] = { 0x00, 0x00, 0x00, 0x72, 0x50, 0xa7, 0xc9, 0x27, 0x4a, 0xfa, 0xb8, 0x77, 0x80, 0x70, 0x22, 0xda, 0xb8, 0x3b, 0x06, 0x50, 0x53, 0x4a, 0x16, 0xe2, 0x65, 0xba, 0xe4, 0x43, 0x6f, 0xe3, 0x29, 0x36, 0x18, 0xe3, 0x45, 0x01, 0x07, 0x20, 0x89, 0x4b, 0x64, 0x5e, 0x89, 0xe1, 0x53, 0x5b, 0xbd, 0xad, 0x5b, 0x8b, 0x29, 0x06, 0x50, 0x53, 0x08, 0x01, 0xb1, 0x8e, 0xbf, 0xbf, 0x5e, 0x8f, 0xab, 0x3c, 0x82, 0x87, 0x2a, 0x3e, 0x9b, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0x32, 0xa3, 0x49, 0x76, 0x5c, 0x5b, 0x35, 0x9a, 0x93, 0x3c, 0x6f, 0x3c, 0x63, 0x6d, 0xc0, 0x00 }; Send(response, 119); return; } // Check for a possible IP ban on this client. BAN_STATUS ipb = IPBanner::getSingleton().CalculateBanStatus(GetRemoteAddress()); if(ipb != BAN_STATUS_NOT_BANNED) LOG_DETAIL("[AuthChallenge] Client %s is banned, refusing to continue.", GetRemoteIP().c_str()); switch(ipb) { case BAN_STATUS_PERMANENT_BAN: SendChallengeError(CE_ACCOUNT_CLOSED); return; case BAN_STATUS_TIME_LEFT_ON_BAN: SendChallengeError(CE_ACCOUNT_FREEZED); return; default: break; } // Null-terminate the account string if(m_challenge.I_len >= 50) { Disconnect(); return; } m_challenge.I[m_challenge.I_len] = 0; // Clear the shitty hash (for server) string AccountName = (char*)&m_challenge.I; string::size_type i = AccountName.rfind("#"); if(i != string::npos) { LOG_ERROR("# ACCOUNTNAME!"); return; //AccountName.erase( i ); } // Look up the account information LOG_DEBUG("[AuthChallenge] Account Name: \"%s\"", AccountName.c_str()); m_account = AccountMgr::getSingleton().GetAccount(AccountName); if(m_account == 0) { LOG_DEBUG("[AuthChallenge] Invalid account."); // Non-existant account SendChallengeError(CE_NO_ACCOUNT); return; } LOG_DEBUG("[AuthChallenge] Account banned state = %u", m_account->Banned); // Check that the account isn't banned. if(m_account->Banned == 1) { SendChallengeError(CE_ACCOUNT_CLOSED); return; } else if(m_account->Banned > 0) { SendChallengeError(CE_ACCOUNT_FREEZED); return; } // update cached locale if(!m_account->forcedLocale) { char temp[4]; temp[0] = m_challenge.country[3]; temp[1] = m_challenge.country[2]; temp[2] = m_challenge.country[1]; temp[3] = m_challenge.country[0]; *(uint32*)&m_account->Locale[0] = *(uint32*)temp; } //////////////////////////////////////////////// SRP6 Challenge //////////////////////////////////////////////// // // // First we will generate the Verifier value using the following formulas // // x = SHA1(s | SHA1(I | ":" | P)) // v = g^x % N // // The SHA1(I | ":" | P) part for x we have in the account database, this is the encrypted password, reversed // N is a safe prime // g is the generator // | means concatenation in this contect // // Sha1Hash sha; sha.UpdateData(s.AsByteArray(), 32); sha.UpdateData(m_account->SrpHash, 20); sha.Finalize(); BigNumber x; x.SetBinary(sha.GetDigest(), sha.GetLength()); v = g.ModExp(x, N); // Next we generate b, and B which are the public and private values of the server // // b = random() // B = k*v + g^b % N // // in our case the multiplier parameters, k = 3 b.SetRand(152); uint8 k = 3; BigNumber gmod = g.ModExp(b, N); B = ((v * k) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk; unk.SetRand(128); // Now we send B, g, N and s to the client as a challenge, asking the client for the proof sAuthLogonChallenge_S challenge; challenge.cmd = 0; challenge.error = 0; challenge.unk2 = CE_SUCCESS; memcpy(challenge.B, B.AsByteArray(), 32); challenge.g_len = 1; challenge.g = (g.AsByteArray())[ 0 ]; challenge.N_len = 32; memcpy(challenge.N, N.AsByteArray(), 32); memcpy(challenge.s, s.AsByteArray(), 32); memcpy(challenge.unk3, unk.AsByteArray(), 16); challenge.unk4 = 0; Send(reinterpret_cast< uint8* >(&challenge), sizeof(sAuthLogonChallenge_S)); }
/// Logon Challenge command handler bool AuthSocket::_HandleLogonChallenge() { DEBUG_LOG("Entering _HandleLogonChallenge"); if (ibuf.GetLength() < sizeof(sAuthLogonChallenge_C)) return false; ///- Read the first 4 bytes (header) to get the length of the remaining of the packet std::vector<uint8> buf; buf.resize(4); ibuf.Read((char *)&buf[0], 4); EndianConvert(*((uint16*)(buf[0]))); uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size; DEBUG_LOG("[AuthChallenge] got header, body is %#04x bytes", remaining); if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (ibuf.GetLength() < remaining)) return false; //No big fear of memory outage (size is int16, i.e. < 65536) buf.resize(remaining + buf.size() + 1); buf[buf.size() - 1] = 0; sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0]; // BigEndian code, nop in little endian case // size already converted EndianConvert(*((uint32*)(&ch->gamename[0]))); EndianConvert(ch->build); EndianConvert(*((uint32*)(&ch->platform[0]))); EndianConvert(*((uint32*)(&ch->os[0]))); EndianConvert(*((uint32*)(&ch->country[0]))); EndianConvert(ch->timezone_bias); EndianConvert(ch->ip); ///- Read the remaining of the packet ibuf.Read((char *)&buf[4], remaining); DEBUG_LOG("[AuthChallenge] got full packet, %#04x bytes", ch->size); DEBUG_LOG("[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I); ByteBuffer pkt; _login = (const char*)ch->I; _build = ch->build; ///- Normalize account name //utf8ToUpperOnlyLatin(_login); -- client already send account in expected form //Escape the user login to avoid further SQL injection //Memory will be freed on AuthSocket object destruction _safelogin = _login; loginDatabase.escape_string(_safelogin); pkt << (uint8) AUTH_LOGON_CHALLENGE; pkt << (uint8) 0x00; ///- Verify that this IP is not in the ip_banned table // No SQL injection possible (paste the IP address as passed by the socket) loginDatabase.Execute("DELETE FROM ip_banned WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate"); std::string address = GetRemoteAddress(); loginDatabase.escape_string(address); QueryResult *result = loginDatabase.PQuery("SELECT * FROM ip_banned WHERE ip = '%s'",address.c_str()); if(result) { pkt << (uint8)REALM_AUTH_ACCOUNT_BANNED; sLog.outBasic("[AuthChallenge] Banned ip %s tries to login!",GetRemoteAddress().c_str ()); delete result; } else { ///- Get the account details from the account table // No SQL injection (escaped user name) result = loginDatabase.PQuery("SELECT sha_pass_hash,id,locked,last_ip,gmlevel,v,s FROM account WHERE username = '******'",_safelogin.c_str ()); if( result ) { ///- If the IP is 'locked', check that the player comes indeed from the correct IP address bool locked = false; if((*result)[2].GetUInt8() == 1) // if ip is locked { DEBUG_LOG("[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), (*result)[3].GetString()); DEBUG_LOG("[AuthChallenge] Player address is '%s'", GetRemoteAddress().c_str()); if ( strcmp((*result)[3].GetString(),GetRemoteAddress().c_str()) ) { DEBUG_LOG("[AuthChallenge] Account IP differs"); pkt << (uint8) REALM_AUTH_ACCOUNT_FREEZED; locked=true; } else { DEBUG_LOG("[AuthChallenge] Account IP matches"); } } else { DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); } if (!locked) { //set expired bans to inactive loginDatabase.Execute("UPDATE account_banned SET active = 0 WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate"); ///- If the account is banned, reject the logon attempt QueryResult *banresult = loginDatabase.PQuery("SELECT bandate,unbandate FROM account_banned WHERE id = %u AND active = 1", (*result)[1].GetUInt32()); if(banresult) { if((*banresult)[0].GetUInt64() == (*banresult)[1].GetUInt64()) { pkt << (uint8) REALM_AUTH_ACCOUNT_BANNED; sLog.outBasic("[AuthChallenge] Banned account %s tries to login!",_login.c_str ()); } else { pkt << (uint8) REALM_AUTH_ACCOUNT_FREEZED; sLog.outBasic("[AuthChallenge] Temporarily banned account %s tries to login!",_login.c_str ()); } delete banresult; } else { ///- Get the password from the account table, upper it, and make the SRP6 calculation std::string rI = (*result)[0].GetCppString(); ///- Don't calculate (v, s) if there are already some in the database std::string databaseV = (*result)[5].GetCppString(); std::string databaseS = (*result)[6].GetCppString(); sLog.outDebug("database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); // multiply with 2, bytes are stored as hexstring if(databaseV.size() != s_BYTE_SIZE*2 || databaseS.size() != s_BYTE_SIZE*2) _SetVSFields(rI); else { s.SetHexStr(databaseS.c_str()); v.SetHexStr(databaseV.c_str()); } b.SetRand(19 * 8); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16 * 8); ///- Fill the response packet with the result pkt << uint8(REALM_AUTH_SUCCESS); // B may be calculated < 32B so we force minimal length to 32B pkt.append(B.AsByteArray(32), 32); // 32 bytes pkt << uint8(1); pkt.append(g.AsByteArray(), 1); pkt << uint8(32); pkt.append(N.AsByteArray(32), 32); pkt.append(s.AsByteArray(), s.GetNumBytes());// 32 bytes pkt.append(unk3.AsByteArray(16), 16); uint8 securityFlags = 0; pkt << uint8(securityFlags); // security flags (0x0...0x04) if(securityFlags & 0x01) // PIN input { pkt << uint32(0); pkt << uint64(0) << uint64(0); // 16 bytes hash? } if(securityFlags & 0x02) // Matrix input { pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint64(0); } if(securityFlags & 0x04) // Security token input { pkt << uint8(1); } uint8 secLevel = (*result)[4].GetUInt8(); _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; _localizationName.resize(4); for(int i = 0; i < 4; ++i) _localizationName[i] = ch->country[4-i-1]; sLog.outBasic("[AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", _login.c_str (), ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName)); // user authenticated => turn off autoreg, thus account creating _autoreg = false; } } delete result; } else if(_autoreg) // no account { // check username if(_safelogin.find_first_of(notAllowedChars)!=_safelogin.npos || _safelogin.length()<4) _autoreg = false; // check IP else if(uint32 amountip = sConfig.GetIntDefault("AmountIP", 0)) { QueryResult *result2 = loginDatabase.PQuery("SELECT COUNT(last_ip) FROM account WHERE last_ip = '%s'", GetRemoteAddress().c_str()); if (result2 && (*result2)[0].GetUInt8() >= amountip) { _autoreg = false; delete result2; } } // still all ok if(_autoreg) { ///- Get the password from the account table, upper it, and make the SRP6 calculation std::transform(_safelogin.begin(), _safelogin.end(), _safelogin.begin(), std::towupper); Sha1Hash sha; std::string sI = _safelogin + ":" + _safelogin; sha.UpdateData(sI); sha.Finalize(); BigNumber bn; bn.SetBinary(sha.GetDigest(), sha.GetLength()); uint8 *val = bn.AsByteArray(); std::reverse(val, val+bn.GetNumBytes()); bn.SetBinary(val, bn.GetNumBytes()); const char* rI = bn.AsHexStr(); _SetVSFields(rI); OPENSSL_free((void*)rI); b.SetRand(19 * 8); BigNumber gmod=g.ModExp(b, N); B = ((v * 3) + gmod) % N; if (B.GetNumBytes() < 32) sLog.outDetail("Interesting, calculation of B in realmd is < 32."); ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16*8); ///- Fill the response packet with the result pkt << (uint8)REALM_AUTH_SUCCESS; pkt.append(B.AsByteArray(), 32); pkt << (uint8)1; pkt.append(g.AsByteArray(), 1); pkt << (uint8)32; pkt.append(N.AsByteArray(), 32); pkt.append(s.AsByteArray(), s.GetNumBytes()); pkt.append(unk3.AsByteArray(), 16); pkt << (uint8)0; // Added in 1.12.x client branch } else // username and/or IP is bad pkt << (uint8) REALM_AUTH_NO_MATCH; } else { // autoreg off in config, account is wrong pkt << (uint8) REALM_AUTH_NO_MATCH; } } SendBuf((char const*)pkt.contents(), pkt.size()); return true; }
void AccountMgr::UpdateAccount(Account* acct, Field* field) { uint32 id = field[0].GetUInt32(); Sha1Hash hash; std::string Username = field[1].GetString(); std::string EncryptedPassword = field[2].GetString(); if(id != acct->AccountId) { LOG_ERROR(" >> deleting duplicate account %u [%s]...", id, Username.c_str()); sLogonSQL->Execute("DELETE FROM accounts WHERE id = %u", id); return; } acct->AccountId = field[0].GetUInt32(); acct->AccountFlags = field[3].GetUInt8(); acct->Banned = field[4].GetUInt32(); if((uint32)UNIXTIME > acct->Banned && acct->Banned != 0 && acct->Banned != 1) //1 = perm ban? { //Accounts should be unbanned once the date is past their set expiry date. acct->Banned = 0; LOG_DEBUG("Account %s's ban has expired.", acct->UsernamePtr->c_str()); sLogonSQL->Execute("UPDATE accounts SET banned = 0 WHERE id = %u", acct->AccountId); } if(strcmp(field[5].GetString(), "enUS")) { // non-standard language forced memcpy(acct->Locale, field[5].GetString(), 4); acct->forcedLocale = true; } else acct->forcedLocale = false; acct->Muted = field[6].GetUInt32(); if((uint32)UNIXTIME > acct->Muted && acct->Muted != 0 && acct->Muted != 1) //1 = perm ban? { //Accounts should be unbanned once the date is past their set expiry date. acct->Muted = 0; LOG_DEBUG("Account %s's mute has expired.", acct->UsernamePtr->c_str()); sLogonSQL->Execute("UPDATE accounts SET muted = 0 WHERE id = %u", acct->AccountId); } // Convert username to uppercase. this is needed ;) Util::StringToUpperCase(Username); // prefer encrypted passwords over nonencrypted if(EncryptedPassword.size() > 0) { if(EncryptedPassword.size() == 40) { BigNumber bn; bn.SetHexStr(EncryptedPassword.c_str()); if(bn.GetNumBytes() < 20) { // Hacky fix memcpy(acct->SrpHash, bn.AsByteArray(), bn.GetNumBytes()); for(int n = bn.GetNumBytes(); n <= 19; n++) acct->SrpHash[n] = (uint8)0; reverse_array(acct->SrpHash, 20); } else { memcpy(acct->SrpHash, bn.AsByteArray(), 20); reverse_array(acct->SrpHash, 20); } } else { LOG_ERROR("Account `%s` has incorrect number of bytes in encrypted password! Disabling.", Username.c_str()); memset(acct->SrpHash, 0, 20); } } else { // This should never happen... LOG_ERROR("Account `%s` has no encrypted password!", Username.c_str()); } }
void AuthSocket::HandleChallenge() { // No header if(readBuffer.GetContiguiousBytes() < 4){ LOG_ERROR( "[AuthChallenge] Packet has no header. Refusing to handle." ); return; } // Check the rest of the packet is complete. uint8 * ReceiveBuffer = (uint8*)readBuffer.GetBufferStart(); uint16 full_size = *(uint16*)&ReceiveBuffer[2]; LOG_DETAIL("[AuthChallenge] got header, body is %u bytes", full_size ); if(readBuffer.GetSize() < uint32(full_size+4)){ LOG_ERROR( "[AuthChallenge] Packet is smaller than expected, refusing to handle" ); return; } // Copy the data into our cached challenge structure if(full_size > sizeof(sAuthLogonChallenge_C_BattleNet)) { LOG_ERROR( "[AuthChallenge] Packet is larger than expected, refusing to handle!" ); Disconnect(); return; } LOG_DEBUG("[AuthChallenge] got a complete packet."); //memcpy(&m_challenge, ReceiveBuffer, full_size + 4); //RemoveReadBufferBytes(full_size + 4, true); readBuffer.Read(&m_challenge, full_size + 4); // Check client build. uint16 build = m_challenge.build; // Check client build. if(build > LogonServer::getSingleton().max_build) { // wtf? LOG_DETAIL( "[AuthChallenge] Client %s has wrong version. More up to date than server. Server: %u, Client: %u", GetRemoteIP().c_str(), LogonServer::getSingleton().max_build, m_challenge.build ); SendChallengeError(CE_WRONG_BUILD_NUMBER); return; } if(build < LogonServer::getSingleton().min_build) { // can we patch? char flippedloc[5] = {0,0,0,0,0}; flippedloc[0] = m_challenge.country[3]; flippedloc[1] = m_challenge.country[2]; flippedloc[2] = m_challenge.country[1]; flippedloc[3] = m_challenge.country[0]; m_patch = PatchMgr::getSingleton().FindPatchForClient(build, flippedloc); if(m_patch == NULL) { // could not find a valid patch LOG_DETAIL( "[AuthChallenge] Client %s has wrong version. More up to date than server. Server: %u, Client: %u", GetRemoteIP().c_str(), LogonServer::getSingleton().min_build, m_challenge.build ); SendChallengeError(CE_WRONG_BUILD_NUMBER); return; } Log.Debug("Patch", "Selected patch %u%s for client.", m_patch->Version,m_patch->Locality); uint8 response[119] = { 0x00, 0x00, 0x00, 0x72, 0x50, 0xa7, 0xc9, 0x27, 0x4a, 0xfa, 0xb8, 0x77, 0x80, 0x70, 0x22, 0xda, 0xb8, 0x3b, 0x06, 0x50, 0x53, 0x4a, 0x16, 0xe2, 0x65, 0xba, 0xe4, 0x43, 0x6f, 0xe3, 0x29, 0x36, 0x18, 0xe3, 0x45, 0x01, 0x07, 0x20, 0x89, 0x4b, 0x64, 0x5e, 0x89, 0xe1, 0x53, 0x5b, 0xbd, 0xad, 0x5b, 0x8b, 0x29, 0x06, 0x50, 0x53, 0x08, 0x01, 0xb1, 0x8e, 0xbf, 0xbf, 0x5e, 0x8f, 0xab, 0x3c, 0x82, 0x87, 0x2a, 0x3e, 0x9b, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0x32, 0xa3, 0x49, 0x76, 0x5c, 0x5b, 0x35, 0x9a, 0x93, 0x3c, 0x6f, 0x3c, 0x63, 0x6d, 0xc0, 0x00 }; Send(response, 119); return; } // Check for a possible IP ban on this client. BAN_STATUS ipb = IPBanner::getSingleton().CalculateBanStatus(GetRemoteAddress()); if( ipb != BAN_STATUS_NOT_BANNED ) LOG_DETAIL( "[AuthChallenge] Client %s is banned, refusing to continue.", GetRemoteIP().c_str() ); switch(ipb) { case BAN_STATUS_PERMANENT_BAN: SendChallengeError(CE_ACCOUNT_CLOSED); return; case BAN_STATUS_TIME_LEFT_ON_BAN: SendChallengeError(CE_ACCOUNT_FREEZED); return; default: break; } // Null-terminate the account string if(m_challenge.I_len >= 50) { Disconnect(); return; } m_challenge.I[m_challenge.I_len] = 0; // Clear the shitty hash (for server) string AccountName = (char*)&m_challenge.I; if (AccountName.substr(0, 1) == "?") { if (AccountName.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?") != string::npos) { LOG_ERROR("[AuthChallenge]: Tried to create account with illegal characters."); SendChallengeError(CE_NO_ACCOUNT); //Well f**k you for editing the files! return; } int pass_start = AccountName.find("?", 1) + 1; if (pass_start < 4) //No username { LOG_ERROR("[AuthChallenge] Tried to create account with no account name."); SendChallengeError(CE_NO_ACCOUNT); return; } int pass_end = AccountName.rfind("?"); if (pass_end <= pass_start) //No password { LOG_ERROR("[AuthChallenge] Tried to create account with no password."); SendChallengeError(CE_NO_ACCOUNT); return; } int name_len = pass_start - 2; int pass_len = pass_end - pass_start; string username = AccountName.substr(1, name_len); string password = AccountName.substr(pass_start, pass_len); m_account = AccountMgr::getSingleton().GetAccount(username); if (m_account != 0) { LOG_ERROR("[AuthChallenge] Account creation failed: account name already taken."); SendChallengeError(CE_ACCOUNT_IN_USE); return; } string cmd = username + " " + password + " NULL"; //Prepare command for CreateAccount char acct[50]; memcpy(acct, cmd.c_str(), 50); //CreateAccount doesn't take const char* LogonConsole::getSingleton().CreateAccount(acct); SendChallengeError(CE_SERVER_FULL); //Success! LOG_BASIC("[AuthChallenge] Client %s has created an account with name: \"%s\"", GetRemoteIP().c_str(), username.c_str()); return; } if (AccountName.substr(0, 1) == "&") { if (AccountName.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?.~&_-") != string::npos) { LOG_ERROR("[AuthChallenge]: Tried to update account with illegal chars. %s", AccountName.c_str()); SendChallengeError(CE_NO_ACCOUNT); //Well f**k you for editing the files! return; } int pass_start = AccountName.find("&", 1) + 1; if (pass_start < 4) //No username { LOG_ERROR("[AuthChallenge] Tried to update account with no account name."); SendChallengeError(CE_NO_ACCOUNT); return; } int pass_end = AccountName.rfind("&"); if (pass_end <= pass_start) //No password { LOG_ERROR("[AuthChallenge] Tried to update account with no email."); SendChallengeError(CE_NO_ACCOUNT); return; } int name_len = pass_start - 2; int pass_len = pass_end - pass_start; string username = AccountName.substr(1, name_len); string email = AccountName.substr(pass_start, pass_len); for (uint8 i = 0; i < email.length(); i++) { if (email[i] == '~') { email[i] = '@'; break; } } m_account = AccountMgr::getSingleton().GetAccount(username); if (m_account == 0) { LOG_ERROR("[AuthChallenge] Account update failed: account does not exist."); SendChallengeError(CE_ACCOUNT_IN_USE); return; } std::stringstream query; query << "UPDATE `accounts` SET `email` = '" << email << "' WHERE `login` = '" << username << "' AND `email` = 'NULL';"; sLogonSQL->WaitExecuteNA(query.str().c_str()); SendChallengeError(CE_SERVER_FULL); //Success! return; } string::size_type i = AccountName.rfind("#"); if( i != string::npos ) { LOG_ERROR("# ACCOUNTNAME!"); return; //AccountName.erase( i ); } // Look up the account information LOG_DEBUG("[AuthChallenge] Account Name: \"%s\"", AccountName.c_str()); m_account = AccountMgr::getSingleton().GetAccount(AccountName); if(m_account == 0) { LOG_DEBUG("[AuthChallenge] Invalid account."); // Non-existant account SendChallengeError(CE_NO_ACCOUNT); return; } LOG_DEBUG("[AuthChallenge] Account banned state = %u", m_account->Banned); // Check that the account isn't banned. if(m_account->Banned == 1) { SendChallengeError(CE_ACCOUNT_CLOSED); return; } else if(m_account->Banned > 0) { SendChallengeError(CE_ACCOUNT_FREEZED); return; } // update cached locale if(!m_account->forcedLocale) { char temp[4]; temp[0] = m_challenge.country[3]; temp[1] = m_challenge.country[2]; temp[2] = m_challenge.country[1]; temp[3] = m_challenge.country[0]; *(uint32*)&m_account->Locale[0] = *(uint32*)temp; } //////////////////////////////////////////////// SRP6 Challenge //////////////////////////////////////////////// // // // First we will generate the Verifier value using the following formulas // // x = SHA1(s | SHA1(I | ":" | P)) // v = g^x % N // // The SHA1(I | ":" | P) part for x we have in the account database, this is the encrypted password, reversed // N is a safe prime // g is the generator // | means concatenation in this contect // // Sha1Hash sha; sha.UpdateData( s.AsByteArray(), 32 ); sha.UpdateData( m_account->SrpHash, 20 ); sha.Finalize(); BigNumber x; x.SetBinary( sha.GetDigest(), sha.GetLength() ); v = g.ModExp(x, N); // Next we generate b, and B which are the public and private values of the server // // b = random() // B = k*v + g^b % N // // in our case the multiplier parameters, k = 3 b.SetRand(152); uint8 k = 3; BigNumber gmod = g.ModExp(b, N); B = ( ( v * k ) + gmod ) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk; unk.SetRand(128); // Now we send B, g, N and s to the client as a challenge, asking the client for the proof sAuthLogonChallenge_S challenge; challenge.cmd = 0; challenge.error = 0; challenge.unk2 = CE_SUCCESS; memcpy( challenge.B, B.AsByteArray(), 32 ); challenge.g_len = 1; challenge.g = ( g.AsByteArray() )[ 0 ]; challenge.N_len = 32; memcpy( challenge.N, N.AsByteArray(), 32 ); memcpy( challenge.s, s.AsByteArray(), 32 ); memcpy( challenge.unk3, unk.AsByteArray(), 16 ); challenge.unk4 = 0; Send( reinterpret_cast< uint8* >( &challenge ), sizeof( sAuthLogonChallenge_S ) ); }
/// Logon Challenge command handler bool AuthSocket::_HandleLogonChallenge() { DEBUG_LOG("Entering _HandleLogonChallenge"); if (recv_len() < sizeof(sAuthLogonChallenge_C)) return false; ///- Read the first 4 bytes (header) to get the length of the remaining of the packet std::vector<uint8> buf; buf.resize(4); recv((char *)&buf[0], 4); EndianConvert(*((uint16*)(buf[0]))); uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size; DEBUG_LOG("[AuthChallenge] got header, body is %#04x bytes", remaining); if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (recv_len() < remaining)) return false; //No big fear of memory outage (size is int16, i.e. < 65536) buf.resize(remaining + buf.size() + 1); buf[buf.size() - 1] = 0; sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0]; ///- Read the remaining of the packet recv((char *)&buf[4], remaining); DEBUG_LOG("[AuthChallenge] got full packet, %#04x bytes", ch->size); DEBUG_LOG("[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I); // BigEndian code, nop in little endian case // size already converted EndianConvert(*((uint32*)(&ch->gamename[0]))); EndianConvert(ch->build); EndianConvert(*((uint32*)(&ch->platform[0]))); EndianConvert(*((uint32*)(&ch->os[0]))); EndianConvert(*((uint32*)(&ch->country[0]))); EndianConvert(ch->timezone_bias); EndianConvert(*((uint32*)(&ch->ip[0]))); std::stringstream tmpLocalIp; tmpLocalIp << (uint32)ch->ip[0] << "." << (uint32)ch->ip[1] << "." << (uint32)ch->ip[2] << "." << (uint32)ch->ip[3]; localIp_ = tmpLocalIp.str(); ByteBuffer pkt; _login = (const char*)ch->I; _build = ch->build; operatingSystem_ = (const char*)ch->os; // Restore string order as its byte order is reversed std::reverse(operatingSystem_.begin(), operatingSystem_.end()); if (operatingSystem_.size() > 4 || (operatingSystem_ != "Win" && operatingSystem_ != "OSX" && (sRealmList.ChatboxOsName == "" || operatingSystem_ != sRealmList.ChatboxOsName))){ sLog.outLog(LOG_WARDEN, "Client %s got unsupported operating system (%s)", _login.c_str(), operatingSystem_.c_str()); return false; } ///- Normalize account name //utf8ToUpperOnlyLatin(_login); -- client already send account in expected form //Escape the user login to avoid further SQL injection //Memory will be freed on AuthSocket object destruction _safelogin = _login; AccountsDatabase.escape_string(_safelogin); pkt << (uint8) CMD_AUTH_LOGON_CHALLENGE; pkt << (uint8) 0x00; std::string address = get_remote_address(); #ifdef REGEX_NAMESPACE for (PatternList::const_iterator i = pattern_banned.begin(); i != pattern_banned.end(); ++i) { if (REGEX_NAMESPACE::regex_match(address.c_str(), i->first) && REGEX_NAMESPACE::regex_match(localIp_.c_str(), i->second)) { pkt<< (uint8) WOW_FAIL_UNKNOWN_ACCOUNT; send((char const*)pkt.contents(), pkt.size()); return true; } } #endif ///- Verify that this IP is not in the ip_banned table // No SQL injection possible (paste the IP address as passed by the socket) AccountsDatabase.Execute("DELETE FROM ip_banned WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate"); AccountsDatabase.escape_string(address); //Delete ViP AccountsDatabase.Execute("UPDATE account_permissions SET permission_mask = 1 WHERE unsetdate<=UNIX_TIMESTAMP() AND unsetdate<>setdate"); QueryResultAutoPtr result = AccountsDatabase.PQuery("SELECT * FROM ip_banned WHERE ip = '%s'", address.c_str()); if (result) // ip banned { sLog.outBasic("[AuthChallenge] Banned ip %s tries to login!", get_remote_address().c_str()); pkt << uint8(WOW_FAIL_BANNED); send((char const*)pkt.contents(), pkt.size()); return true; } ///- Get the account details from the account table // No SQL injection (escaped user name) result = AccountsDatabase.PQuery("SELECT pass_hash, account.account_id, account_state_id, last_ip, permission_mask, email " "FROM account JOIN account_permissions ON account.account_id = account_permissions.account_id " "WHERE username = '******'", _safelogin.c_str()); if (!result) // account not exists { pkt<< uint8(WOW_FAIL_UNKNOWN_ACCOUNT); send((char const*)pkt.contents(), pkt.size()); return true; } Field * fields = result->Fetch(); ///- If the IP is 'locked', check that the player comes indeed from the correct IP address switch (fields[2].GetUInt8()) { case ACCOUNT_STATE_IP_LOCKED: { DEBUG_LOG("[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), (*result)[3].GetString()); DEBUG_LOG("[AuthChallenge] Player address is '%s'", get_remote_address().c_str()); if (strcmp(fields[4].GetString(), get_remote_address().c_str())) { DEBUG_LOG("[AuthChallenge] Account IP differs"); pkt << (uint8) WOW_FAIL_LOCKED_ENFORCED; send((char const*)pkt.contents(), pkt.size()); return true; } else { DEBUG_LOG("[AuthChallenge] Account IP matches"); } break; } case ACCOUNT_STATE_FROZEN: { pkt << uint8(WOW_FAIL_SUSPENDED); send((char const*)pkt.contents(), pkt.size()); return true; } default: DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip or frozen", _login.c_str()); break; } ///- If the account is banned, reject the logon attempt QueryResultAutoPtr banresult = AccountsDatabase.PQuery("SELECT punishment_date, expiration_date " "FROM account_punishment " "WHERE account_id = '%u' AND punishment_type_id = '%u' AND (punishment_date = expiration_date OR expiration_date > UNIX_TIMESTAMP())", (*result)[1].GetUInt32(), PUNISHMENT_BAN); if (banresult) { if((*banresult)[0].GetUInt64() == (*banresult)[1].GetUInt64()) { pkt << uint8(WOW_FAIL_BANNED); sLog.outBasic("[AuthChallenge] Banned account %s tries to login!", _login.c_str ()); } else { pkt << uint8(WOW_FAIL_SUSPENDED); sLog.outBasic("[AuthChallenge] Temporarily banned account %s tries to login!", _login.c_str ()); } send((char const*)pkt.contents(), pkt.size()); return true; } QueryResultAutoPtr emailbanresult = AccountsDatabase.PQuery("SELECT email FROM email_banned WHERE email = '%s'", (*result)[5].GetString()); if (emailbanresult) { pkt << uint8(WOW_FAIL_BANNED); sLog.outBasic("[AuthChallenge] Account %s with banned email %s tries to login!", _login.c_str (), (*emailbanresult)[0].GetString()); send((char const*)pkt.contents(), pkt.size()); return true; } ///- Get the password from the account table, upper it, and make the SRP6 calculation std::string rI = fields[0].GetCppString(); _SetVSFields(rI); b.SetRand(19 * 8); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16 * 8); ///- Fill the response packet with the result pkt << uint8(WOW_SUCCESS); // B may be calculated < 32B so we force minimal length to 32B pkt.append(B.AsByteArray(32), 32); // 32 bytes pkt << uint8(1); pkt.append(g.AsByteArray(), 1); pkt << uint8(32); pkt.append(N.AsByteArray(32), 32); pkt.append(s.AsByteArray(), s.GetNumBytes());// 32 bytes pkt.append(unk3.AsByteArray(16), 16); uint8 securityFlags = 0; pkt << uint8(securityFlags); // security flags (0x0...0x04) if (securityFlags & 0x01) // PIN input { pkt << uint32(0); pkt << uint64(0) << uint64(0); // 16 bytes hash? } if (securityFlags & 0x02) // Matrix input { pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint64(0); } if (securityFlags & 0x04) // Security token input pkt << uint8(1); accountPermissionMask_ = fields[4].GetUInt64(); _localizationName.resize(4); for (int i = 0; i < 4; ++i) _localizationName[i] = ch->country[4-i-1]; sLog.outBasic("[AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", _login.c_str (), ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName)); send((char const*)pkt.contents(), pkt.size()); return true; }
/// Logon Challenge command handler bool AuthSocket::_HandleLogonChallenge() { sLog.outStaticDebug("Entering _HandleLogonChallenge"); if (socket().recv_len() < sizeof(sAuthLogonChallenge_C)) return false; ///- Read the first 4 bytes (header) to get the length of the remaining of the packet std::vector<uint8> buf; buf.resize(4); socket().recv((char *)&buf[0], 4); EndianConvert(*((uint16*)(buf[0]))); uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size; sLog.outStaticDebug("[AuthChallenge] got header, body is %#04x bytes", remaining); if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (socket().recv_len() < remaining)) return false; //No big fear of memory outage (size is int16, i.e. < 65536) buf.resize(remaining + buf.size() + 1); buf[buf.size() - 1] = 0; sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0]; ///- Read the remaining of the packet socket().recv((char *)&buf[4], remaining); sLog.outStaticDebug("[AuthChallenge] got full packet, %#04x bytes", ch->size); sLog.outStaticDebug("[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I); // BigEndian code, nop in little endian case // size already converted EndianConvert(*((uint32*)(&ch->gamename[0]))); EndianConvert(ch->build); EndianConvert(*((uint32*)(&ch->platform[0]))); EndianConvert(*((uint32*)(&ch->os[0]))); EndianConvert(*((uint32*)(&ch->country[0]))); EndianConvert(ch->timezone_bias); EndianConvert(ch->ip); ByteBuffer pkt; _login = (const char*)ch->I; _build = ch->build; _expversion = (AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : NO_VALID_EXP_FLAG) + (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG); ///- Normalize account name //utf8ToUpperOnlyLatin(_login); -- client already send account in expected form _build = ch->build; pkt << (uint8) AUTH_LOGON_CHALLENGE; pkt << (uint8) 0x00; ///- Verify that this IP is not in the ip_banned table LoginDatabase.Execute( LoginDatabase.GetPreparedStatement(LOGIN_SET_EXPIREDIPBANS) ); const std::string& ip_address = socket().get_remote_address(); PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_IPBANNED); stmt->setString(0, ip_address); PreparedQueryResult result = LoginDatabase.Query(stmt); if (result) { pkt << (uint8)WOW_FAIL_BANNED; sLog.outBasic("[AuthChallenge] Banned ip %s tries to login!", ip_address.c_str()); } else { ///- Get the account details from the account table // No SQL injection (prepared statement) stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_LOGONCHALLENGE); stmt->setString(0, _login); PreparedQueryResult res2 = LoginDatabase.Query(stmt); if (res2) { ///- If the IP is 'locked', check that the player comes indeed from the correct IP address bool locked = false; if (res2->GetUInt8(2) == 1) // if ip is locked { sLog.outStaticDebug("[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), res2->GetCString(3)); sLog.outStaticDebug("[AuthChallenge] Player address is '%s'", ip_address.c_str()); if (strcmp(res2->GetCString(3), ip_address.c_str())) { sLog.outStaticDebug("[AuthChallenge] Account IP differs"); pkt << (uint8) WOW_FAIL_SUSPENDED; locked=true; } else sLog.outStaticDebug("[AuthChallenge] Account IP matches"); } else sLog.outStaticDebug("[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); if (!locked) { //set expired bans to inactive LoginDatabase.Execute( LoginDatabase.GetPreparedStatement(LOGIN_SET_EXPIREDACCBANS) ); ///- If the account is banned, reject the logon attempt stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_ACCBANNED); stmt->setUInt32(0, res2->GetUInt32(1)); PreparedQueryResult banresult = LoginDatabase.Query(stmt); if (banresult) { if (banresult->GetUInt64(0) == banresult->GetUInt64(1)) { pkt << (uint8) WOW_FAIL_BANNED; sLog.outBasic("[AuthChallenge] Banned account %s tries to login!", _login.c_str()); } else { pkt << (uint8) WOW_FAIL_SUSPENDED; sLog.outBasic("[AuthChallenge] Temporarily banned account %s tries to login!", _login.c_str()); } } else { ///- Get the password from the account table, upper it, and make the SRP6 calculation std::string rI = res2->GetString(0); ///- Don't calculate (v, s) if there are already some in the database std::string databaseV = res2->GetString(5); std::string databaseS = res2->GetString(6); sLog.outDebug("database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); // multiply with 2, bytes are stored as hexstring if (databaseV.size() != s_BYTE_SIZE*2 || databaseS.size() != s_BYTE_SIZE*2) _SetVSFields(rI); else { s.SetHexStr(databaseS.c_str()); v.SetHexStr(databaseV.c_str()); } b.SetRand(19 * 8); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16 * 8); ///- Fill the response packet with the result pkt << uint8(WOW_SUCCESS); // B may be calculated < 32B so we force minimal length to 32B pkt.append(B.AsByteArray(32), 32); // 32 bytes pkt << uint8(1); pkt.append(g.AsByteArray(), 1); pkt << uint8(32); pkt.append(N.AsByteArray(32), 32); pkt.append(s.AsByteArray(), s.GetNumBytes()); // 32 bytes pkt.append(unk3.AsByteArray(16), 16); uint8 securityFlags = 0; pkt << uint8(securityFlags); // security flags (0x0...0x04) if (securityFlags & 0x01) // PIN input { pkt << uint32(0); pkt << uint64(0) << uint64(0); // 16 bytes hash? } if (securityFlags & 0x02) // Matrix input { pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint64(0); } if (securityFlags & 0x04) // Security token input pkt << uint8(1); uint8 secLevel = res2->GetUInt8(4); _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; _localizationName.resize(4); for (int i = 0; i < 4; ++i) _localizationName[i] = ch->country[4-i-1]; sLog.outBasic("[AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", _login.c_str (), ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName)); } } } else //no account { pkt<< (uint8) WOW_FAIL_UNKNOWN_ACCOUNT; } } socket().send((char const*)pkt.contents(), pkt.size()); return true; }
void AuthSocket::HandleChallenge() { // No header if(GetReadBuffer()->GetSize() < 4) return; if(sInfoCore.GetRealmMap().empty()) { // If we lack a realm to connect to, block em, it's better then making them sit and get locked into an empty list. SendChallengeError(CE_IPBAN); return; } // Check the rest of the packet is complete. uint8 * ReceiveBuffer = (uint8*)GetReadBuffer()->GetBufferOffset(); uint16 full_size = *(uint16*)&ReceiveBuffer[2]; DEBUG_LOG("AuthChallenge","got header, body is 0x%02X bytes", full_size); if(GetReadBuffer()->GetSize() < uint32(full_size+4)) return; // Copy the data into our cached challenge structure if(full_size > sizeof(sAuthLogonChallenge_C)) { Disconnect(); return; } DEBUG_LOG("AuthChallenge","got full packet."); GetReadBuffer()->Read(&m_challenge, full_size + 4); // Check client build. if(GetBuild() > LogonServer::getSingleton().max_build) { SendChallengeError(CE_WRONG_BUILD_NUMBER); return; } if(GetBuild() < LogonServer::getSingleton().min_build) { // can we patch? char flippedloc[5] = {0,0,0,0,0}; flippedloc[0] = m_challenge.country[3]; flippedloc[1] = m_challenge.country[2]; flippedloc[2] = m_challenge.country[1]; flippedloc[3] = m_challenge.country[0]; m_patch = PatchMgr::getSingleton().FindPatchForClient(GetBuild(), flippedloc); if(m_patch == NULL) { // could not find a valid patch SendChallengeError(CE_WRONG_BUILD_NUMBER); return; } DEBUG_LOG("Patch", "Selected patch %u%s for client.", m_patch->Version,m_patch->Locality); BigNumber unk; unk.SetRand(128); uint8 response[119] = { 0x00, 0x00, 0x00, 0x72, 0x50, 0xa7, 0xc9, 0x27, 0x4a, 0xfa, 0xb8, 0x77, 0x80, 0x70, 0x22, 0xda, 0xb8, 0x3b, 0x06, 0x50, 0x53, 0x4a, 0x16, 0xe2, 0x65, 0xba, 0xe4, 0x43, 0x6f, 0xe3, 0x29, 0x36, 0x18, 0xe3, 0x45, 0x01, 0x07, 0x20, 0x89, 0x4b, 0x64, 0x5e, 0x89, 0xe1, 0x53, 0x5b, 0xbd, 0xad, 0x5b, 0x8b, 0x29, 0x06, 0x50, 0x53, 0x08, 0x01, 0xb1, 0x8e, 0xbf, 0xbf, 0x5e, 0x8f, 0xab, 0x3c, 0x82, 0x87, 0x2a, 0x3e, 0x9b, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0x32, 0xa3, 0x49, 0x76, 0x5c, 0x5b, 0x35, 0x9a, 0x93, 0x3c, 0x6f, 0x3c, 0x63, 0x6d, 0xc0, 0x00 }; Send(response, 119); return; } // Check for a possible IP ban on this client. BAN_STATUS ipb = IPBanner::getSingleton().CalculateBanStatus(GetRemoteAddress()); switch(ipb) { case BAN_STATUS_PERMANENT_BAN: SendChallengeError(CE_ACCOUNT_CLOSED); return; case BAN_STATUS_TIME_LEFT_ON_BAN: SendChallengeError(CE_ACCOUNT_FREEZED); return; default: break; } // Null-terminate the account string if(m_challenge.I_len >= 0x50) { Disconnect(); return; } m_challenge.I[m_challenge.I_len] = 0; AccountName = (char*)&m_challenge.I; string::size_type i = AccountName.rfind("#"); if( i != string::npos ) { printf("# ACCOUNTNAME!\n"); return; } // Look up the account information m_account = AccountMgr::getSingleton().GetAccount(AccountName); if(m_account == 0) { DEBUG_LOG("AuthChallenge","Account Name: \"%s\" - Account state: INVALID", AccountName.c_str()); // Non-existant account SendChallengeError(CE_NO_ACCOUNT); return; } // Check that the account isn't banned. if(m_account->Banned == 1) { SendChallengeError(CE_ACCOUNT_CLOSED); Log.Notice("AuthChallenge","Account Name: \"%s\" - Account state: CLOSED", AccountName.c_str()); return; } else if(m_account->Banned > 0) { SendChallengeError(CE_ACCOUNT_FREEZED); Log.Notice("AuthChallenge","Account Name: \"%s\" - Account state: FROZEN (%u)", AccountName.c_str(), m_account->Banned); return; } else Log.Notice("AuthChallenge","Account Name: \"%s\" - Account state: OK", AccountName.c_str()); // update cached locale if(!m_account->forcedLocale) { char temp[4]; temp[0] = m_challenge.country[3]; temp[1] = m_challenge.country[2]; temp[2] = m_challenge.country[1]; temp[3] = m_challenge.country[0]; *(uint32*)&m_account->Locale[0] = *(uint32*)temp; } Sha1Hash sha; //uint32 tc = s.GetNumBytes(); sha.UpdateData( s.AsByteArray(), 32 ); sha.UpdateData( m_account->SrpHash, 20 ); sha.Finalize(); BigNumber x; x.SetBinary( sha.GetDigest(), sha.GetLength() ); v = g.ModExp(x, N); b.SetRand(152); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk; unk.SetRand(128); uint8 response[200]; uint32 c = 0; response[c] = 0; c += 1; response[c] = 0; c += 1; response[c] = CE_SUCCESS; c += 1; memcpy(&response[c], B.AsByteArray(), 32); c += 32; response[c] = 1; c += 1; response[c] = g.AsByteArray()[0]; c += 1; response[c] = 32; c += 1; memcpy(&response[c], N.AsByteArray(), 32); c += 32; memcpy(&response[c], s.AsByteArray(), s.GetNumBytes()); c += s.GetNumBytes(); memcpy(&response[c], unk.AsByteArray(), 16); c += 16; response[c] = 0; c += 1; Send(response, c); DEBUG_LOG("AuthSocket","Sending Success Response"); }
/// Logon Challenge command handler bool AuthSocket::_HandleLogonChallenge() { DEBUG_LOG("Entering _HandleLogonChallenge"); if (recv_len() < sizeof(sAuthLogonChallenge_C)) return false; ///- Read the first 4 bytes (header) to get the length of the remaining of the packet std::vector<uint8> buf; buf.resize(4); recv((char *)&buf[0], 4); EndianConvert(*((uint16*)(buf[0]))); uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size; DEBUG_LOG("[AuthChallenge] got header, body is %#04x bytes", remaining); if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (recv_len() < remaining)) return false; //No big fear of memory outage (size is int16, i.e. < 65536) buf.resize(remaining + buf.size() + 1); buf[buf.size() - 1] = 0; sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0]; ///- Read the remaining of the packet recv((char *)&buf[4], remaining); DEBUG_LOG("[AuthChallenge] got full packet, %#04x bytes", ch->size); DEBUG_LOG("[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I); // BigEndian code, nop in little endian case // size already converted EndianConvert(*((uint32*)(&ch->gamename[0]))); EndianConvert(ch->build); EndianConvert(*((uint32*)(&ch->platform[0]))); EndianConvert(*((uint32*)(&ch->os[0]))); EndianConvert(*((uint32*)(&ch->country[0]))); EndianConvert(ch->timezone_bias); EndianConvert(ch->ip); ByteBuffer pkt; _login = (const char*)ch->I; _build = ch->build; _os = (const char*)ch->os; if(_os.size() > 4) return false; ///- Normalize account name //utf8ToUpperOnlyLatin(_login); -- client already send account in expected form //Escape the user login to avoid further SQL injection //Memory will be freed on AuthSocket object destruction _safelogin = _login; LoginDatabase.escape_string(_safelogin); // Starting CMD_AUTH_LOGON_CHALLENGE AuthResult result = WOW_FAIL_UNKNOWN0; ///- Verify that this IP is not in the ip_banned table // No SQL injection possible (paste the IP address as passed by the socket) std::string address = get_remote_address(); LoginDatabase.escape_string(address); QueryResult* qresult = LoginDatabase.PQuery("SELECT unbandate FROM ip_banned WHERE " // permanent still banned "(unbandate = bandate OR unbandate > UNIX_TIMESTAMP()) AND ip = '%s'", address.c_str()); if (qresult) { result = WOW_FAIL_BANNED; BASIC_LOG("[AuthChallenge] Banned ip %s tries to login!", get_remote_address().c_str()); delete qresult; } else { ///- Get the account details from the account table // No SQL injection (escaped user name) //qresult = LoginDatabase.PQuery("SELECT sha_pass_hash,id,locked,last_ip,gmlevel,v,s FROM account WHERE username = '******'",_safelogin.c_str()); qresult = LoginDatabase.PQuery("SELECT a.sha_pass_hash,a.id,a.locked,a.last_ip,aa.gmlevel,a.v,a.s FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE username = '******'", _safelogin.c_str()); if (qresult) { std::string rI = (*qresult)[0].GetCppString(); uint32 accountId = (*qresult)[1].GetUInt32(); uint8 locked = (*qresult)[2].GetUInt8(); std::string lastIP = (*qresult)[3].GetString(); uint8 secLevel = (*qresult)[4].GetUInt8(); std::string databaseV = (*qresult)[5].GetCppString(); std::string databaseS = (*qresult)[6].GetCppString(); bool blockLogin = false; if (sConfig.GetBoolDefault("MultiIPCheck", false)) { int32 iplimit = sConfig.GetIntDefault("MultiIPLimit", 10); int32 multiIPdelay = sConfig.GetIntDefault("MultiIPPeriodInHours", 48); // If a GM account login ignore MultiIP QueryResult* ipcheck = LoginDatabase.PQuery("SELECT id FROM account WHERE last_ip = '%s' AND id != %u AND last_login > NOW() - INTERVAL %u HOUR ORDER BY last_login DESC;", get_remote_address().c_str(), accountId, multiIPdelay); if (ipcheck) { // build whitelist std::list<uint32> accountsInWhitelist; accountsInWhitelist.clear(); QueryResult* IDsinwhite = LoginDatabase.PQuery("SELECT whitelist FROM multi_IP_whitelist WHERE whitelist LIKE '%|%u|%'", accountId); if (IDsinwhite) { Tokens whitelistaccounts((*IDsinwhite)[0].GetCppString(),'|'); bool isInWhite = false; for (Tokens::const_iterator itr = whitelistaccounts.begin(); itr != whitelistaccounts.end(); ++itr) accountsInWhitelist.push_back(atoi(*itr)); delete IDsinwhite; } do { Field* pFields =ipcheck->Fetch(); uint32 MultiAccountID = pFields[0].GetUInt32(); bool isInWhite = false; for (std::list<uint32>::const_iterator itr = accountsInWhitelist.begin(); itr != accountsInWhitelist.end(); ++itr) { if (*itr == MultiAccountID) isInWhite = true; } if (!isInWhite) { --iplimit; } } while (ipcheck->NextRow()); delete ipcheck; } /* * default case 10 allowed account with same last_ip * we found 9 account with current ip. NOTE: actual account is not in list * 10 - 9 - 1 * ^ current account * ^ account in list * ^ allowed */ if (iplimit < 1) { DEBUG_LOG("[AuthChallenge] Account '%s' is multi IP - '%s'", _login.c_str(), lastIP.c_str()); result = WOW_FAIL_PARENTCONTROL; blockLogin = true; } } ///- If the IP is 'locked', check that the player comes indeed from the correct IP address if (locked == 1) // if ip is locked { DEBUG_LOG("[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), lastIP.c_str()); DEBUG_LOG("[AuthChallenge] Player address is '%s'", get_remote_address().c_str()); if (strcmp(lastIP.c_str(),get_remote_address().c_str()) ) { DEBUG_LOG("[AuthChallenge] Account IP differs"); result = WOW_FAIL_SUSPENDED; blockLogin = true; } else { DEBUG_LOG("[AuthChallenge] Account IP matches"); } } else { DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); } if (!blockLogin) { ///- If the account is banned, reject the logon attempt QueryResult* banresult = LoginDatabase.PQuery("SELECT bandate,unbandate FROM account_banned WHERE " "id = %u AND active = 1 AND (unbandate > UNIX_TIMESTAMP() OR unbandate = bandate)", accountId); if (banresult) { if ((*banresult)[0].GetUInt64() == (*banresult)[1].GetUInt64()) { result = WOW_FAIL_BANNED; BASIC_LOG("[AuthChallenge] Banned account %s (Id: %u) tries to login!", _login.c_str(), accountId); } else { result = WOW_FAIL_SUSPENDED; BASIC_LOG("[AuthChallenge] Temporarily banned account %s (Id: %u) tries to login!",_login.c_str(), accountId); } delete banresult; } else { DEBUG_LOG("database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); // multiply with 2, bytes are stored as hexstring if (databaseV.size() != s_BYTE_SIZE*2 || databaseS.size() != s_BYTE_SIZE*2) _SetVSFields(rI); else { s.SetHexStr(databaseS.c_str()); v.SetHexStr(databaseV.c_str()); } result = WOW_SUCCESS; _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; _localizationName.resize(4); for (int i = 0; i < 4; ++i) _localizationName[i] = ch->country[4-i-1]; BASIC_LOG("[AuthChallenge] account %s (Id: %u) is using '%c%c%c%c' locale (%u)", _login.c_str (), accountId, ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName)); } } delete qresult; } else if (sConfig.GetBoolDefault("AutoRegistration", false)) { if (_safelogin.find_first_of("\t\v\b\f\a\n\r\\\"\'\? <>[](){}_=+-|/!@#$%^&*~`.,\0") == _safelogin.npos && _safelogin.length() > 3) { QueryResult* checkIPresult = LoginDatabase.PQuery("SELECT COUNT(last_ip) FROM account WHERE last_ip = '%s'",get_remote_address().c_str()); int32 regCount = checkIPresult ? (*checkIPresult)[0].GetUInt32() : 0; if (regCount >= sConfig.GetIntDefault("AutoRegistration.Amount", 1)) { BASIC_LOG("[AuthChallenge] Impossible auto-register account %s, number of auto-registered accouts is %u, but allowed only %u", _safelogin.c_str(),regCount, sConfig.GetIntDefault("AutoRegistration.Amount", 1)); // result = WOW_FAIL_DB_BUSY; result = WOW_FAIL_DISCONNECTED; } else { std::transform(_safelogin.begin(), _safelogin.end(), _safelogin.begin(), std::towupper); Sha1Hash sha; sha.Initialize(); sha.UpdateData(_safelogin); sha.UpdateData(":"); sha.UpdateData(_safelogin); sha.Finalize(); std::string encoded; hexEncodeByteArray(sha.GetDigest(), sha.GetLength(), encoded); LoginDatabase.PExecute("INSERT INTO account(username,sha_pass_hash,joindate) VALUES('%s','%s',NOW())", _safelogin.c_str(), encoded.c_str()); _SetVSFields(encoded); BASIC_LOG("[AuthChallenge] account %s auto-registered (count %u)!",_safelogin.c_str(), ++regCount); result = WOW_SUCCESS; _accountSecurityLevel = SEC_PLAYER; _localizationName.resize(4); for (int i = 0; i < 4; ++i) _localizationName[i] = ch->country[4-i-1]; } if (checkIPresult) delete checkIPresult; } } else result = WOW_FAIL_UNKNOWN_ACCOUNT; } pkt << uint8(CMD_AUTH_LOGON_CHALLENGE); pkt << uint8(0x00); pkt << uint8(result); switch (result) { case WOW_SUCCESS: { b.SetRand(19 * 8); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; MANGOS_ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16 * 8); // B may be calculated < 32B so we force minimal length to 32B pkt.append(B.AsByteArray(32), 32); // 32 bytes pkt << uint8(1); pkt.append(g.AsByteArray(), 1); pkt << uint8(32); pkt.append(N.AsByteArray(32), 32); pkt.append(s.AsByteArray(), s.GetNumBytes());// 32 bytes pkt.append(unk3.AsByteArray(16), 16); uint8 securityFlags = 0; pkt << uint8(securityFlags); // security flags (0x0...0x04) if (securityFlags & 0x01) // PIN input { pkt << uint32(0); pkt << uint64(0) << uint64(0); // 16 bytes hash? } if (securityFlags & 0x02) // Matrix input { pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint64(0); } if (securityFlags & 0x04) // Security token input { pkt << uint8(1); } break; } case WOW_FAIL_UNKNOWN0: case WOW_FAIL_UNKNOWN1: case WOW_FAIL_SUSPENDED: case WOW_FAIL_BANNED: case WOW_FAIL_UNKNOWN_ACCOUNT: case WOW_FAIL_INCORRECT_PASSWORD: case WOW_FAIL_ALREADY_ONLINE: case WOW_FAIL_NO_TIME: case WOW_FAIL_DB_BUSY: case WOW_FAIL_VERSION_INVALID: case WOW_FAIL_VERSION_UPDATE: case WOW_FAIL_INVALID_SERVER: case WOW_FAIL_FAIL_NOACCESS: case WOW_SUCCESS_SURVEY: case WOW_FAIL_PARENTCONTROL: case WOW_FAIL_LOCKED_ENFORCED: case WOW_FAIL_TRIAL_ENDED: case WOW_FAIL_USE_BATTLENET: case WOW_FAIL_TOO_FAST: case WOW_FAIL_CHARGEBACK: case WOW_FAIL_GAME_ACCOUNT_LOCKED: case WOW_FAIL_INTERNET_GAME_ROOM_WITHOUT_BNET: case WOW_FAIL_UNLOCKABLE_LOCK: case WOW_FAIL_DISCONNECTED: break; default: BASIC_LOG("[AuthChallenge] unknown CMD_AUTH_LOGON_CHALLENGE execution result %u!", result); break; } send((char const*)pkt.contents(), pkt.size()); return true; }
void AccountMgr::AddAccount(Field* field) { Account* acct = new Account; Sha1Hash hash; std::string Username = field[1].GetString(); std::string EncryptedPassword = field[2].GetString(); acct->AccountId = field[0].GetUInt32(); acct->AccountFlags = field[3].GetUInt8(); acct->Banned = field[4].GetUInt32(); if((uint32)UNIXTIME > acct->Banned && acct->Banned != 0 && acct->Banned != 1) //1 = perm ban? { acct->Banned = 0; sLogonSQL->Execute("UPDATE accounts SET banned = 0 WHERE id = %u", acct->AccountId); } acct->Locale[0] = 'e'; acct->Locale[1] = 'n'; acct->Locale[2] = 'U'; acct->Locale[3] = 'S'; if(strcmp(field[5].GetString(), "enUS")) { // non-standard language forced memcpy(acct->Locale, field[5].GetString(), 4); acct->forcedLocale = true; } else acct->forcedLocale = false; acct->Muted = field[6].GetUInt32(); if((uint32)UNIXTIME > acct->Muted && acct->Muted != 0 && acct->Muted != 1) //1 = perm ban? { acct->Muted = 0; sLogonSQL->Execute("UPDATE accounts SET muted = 0 WHERE id = %u", acct->AccountId); } // Convert username to uppercase. this is needed ;) Util::StringToUpperCase(Username); // prefer encrypted passwords over nonencrypted if(EncryptedPassword.size() > 0) { if(EncryptedPassword.size() == 40) { BigNumber bn; bn.SetHexStr(EncryptedPassword.c_str()); if(bn.GetNumBytes() < 20) { // Hacky fix memcpy(acct->SrpHash, bn.AsByteArray(), bn.GetNumBytes()); for(int n = bn.GetNumBytes(); n <= 19; n++) acct->SrpHash[n] = (uint8)0; reverse_array(acct->SrpHash, 20); } else { memcpy(acct->SrpHash, bn.AsByteArray(), 20); reverse_array(acct->SrpHash, 20); } } else { LOG_ERROR("Account `%s` has incorrect number of bytes in encrypted password! Disabling.", Username.c_str()); memset(acct->SrpHash, 0, 20); } } else { // This should never happen... LOG_ERROR("Account `%s` has no encrypted password!", Username.c_str()); } AccountDatabase[Username] = acct; }
// Logon Challenge command handler bool AuthSocket::_HandleLogonChallenge() { sLog->outStaticDebug("Entering _HandleLogonChallenge"); if (socket().recv_len() < sizeof(sAuthLogonChallenge_C)) return false; // Read the first 4 bytes (header) to get the length of the remaining of the packet std::vector<uint8> buf; buf.resize(4); socket().recv((char *)&buf[0], 4); #if SKYFIRE_ENDIAN == SKYFIRE_BIGENDIAN EndianConvert(*((uint16*)(buf[0]))); #endif uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size; sLog->outStaticDebug("[AuthChallenge] got header, body is %#04x bytes", remaining); if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (socket().recv_len() < remaining)) return false; //No big fear of memory outage (size is int16, i.e. < 65536) buf.resize(remaining + buf.size() + 1); buf[buf.size() - 1] = 0; sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0]; // Read the remaining of the packet socket().recv((char *)&buf[4], remaining); sLog->outStaticDebug("[AuthChallenge] got full packet, %#04x bytes", ch->size); sLog->outStaticDebug("[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I); // BigEndian code, nop in little endian case // size already converted #if SKYFIRE_ENDIAN == SKYFIRE_BIGENDIAN EndianConvert(*((uint32*)(&ch->gamename[0]))); EndianConvert(ch->build); EndianConvert(*((uint32*)(&ch->platform[0]))); EndianConvert(*((uint32*)(&ch->os[0]))); EndianConvert(*((uint32*)(&ch->country[0]))); EndianConvert(ch->timezone_bias); EndianConvert(ch->ip); #endif ByteBuffer pkt; _login = (const char*)ch->I; _build = ch->build; _expversion = (AuthHelper::IsPostWotLKAcceptedClientBuild(_build) ? POST_WOTLK_EXP_FLAG : NO_VALID_EXP_FLAG) | (AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : NO_VALID_EXP_FLAG) | (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG); _os = (const char*)ch->os; if(_os.size() > 4) return false; // Restore string order as its byte order is reversed std::reverse(_os.begin(), _os.end()); pkt << (uint8)AUTH_LOGON_CHALLENGE; pkt << (uint8)0x00; // Verify that this IP is not in the ip_banned table // No SQL injection possible (paste the IP address as passed by the socket) LoginDatabase.Execute("DELETE FROM ip_banned WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate"); std::string address(socket().getRemoteAddress().c_str()); LoginDatabase.EscapeString(address); QueryResult_AutoPtr result = LoginDatabase.PQuery("SELECT * FROM ip_banned WHERE ip = '%s'", address.c_str()); if (result) { pkt << (uint8)WOW_FAIL_BANNED; sLog->outBasic("[AuthChallenge] Banned ip %s tried to login!", address.c_str()); } else { // Get the account details from the account table // No SQL injection (prepared statement) result = LoginDatabase.PQuery("SELECT a.sha_pass_hash,a.id,a.locked,a.last_ip,aa.gmlevel,a.v,a.s " "FROM account a " "LEFT JOIN account_access aa " "ON (a.id = aa.id) " "WHERE a.username = '******'",_login.c_str ()); if (result) { ///- If the IP is 'locked', check that the player comes indeed from the correct IP address bool locked = false; if ((*result)[2].GetUInt8() == 1) // if ip is locked { sLog->outStaticDebug("[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), (*result)[3].GetString()); sLog->outStaticDebug("[AuthChallenge] Player address is '%s'", address.c_str()); if (strcmp((*result)[3].GetString(),socket().getRemoteAddress().c_str())) { sLog->outStaticDebug("[AuthChallenge] Account IP differs"); pkt << (uint8) WOW_FAIL_SUSPENDED; locked = true; } else sLog->outStaticDebug("[AuthChallenge] Account IP matches"); } else sLog->outStaticDebug("[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); if (!locked) { //set expired bans to inactive //LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_SET_EXPIREDACCBANS)); // If the account is banned, reject the logon attempt LoginDatabase.Execute("UPDATE account_banned SET active = 0 WHERE unbandate<=UNIX_TIMESTAMP() AND unbandate<>bandate"); ///- If the account is banned, reject the logon attempt QueryResult_AutoPtr banresult = LoginDatabase.PQuery("SELECT bandate,unbandate FROM account_banned WHERE id = %u AND active = 1", (*result)[1].GetUInt32()); if (banresult) { if ((*banresult)[0].GetUInt64() == (*banresult)[1].GetUInt64()) { pkt << (uint8)WOW_FAIL_BANNED; sLog->outBasic("'%s:%d' [AuthChallenge] Banned account %s tried to login!", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str ()); } else { pkt << (uint8)WOW_FAIL_SUSPENDED; sLog->outBasic("'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str ()); } } else { // Get the password from the account table, upper it, and make the SRP6 calculation std::string rI = (*result)[0].GetCppString(); // Don't calculate (v, s) if there are already some in the database std::string databaseV = (*result)[5].GetCppString(); std::string databaseS = (*result)[6].GetCppString(); sLog->outDebug("database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); // multiply with 2 since bytes are stored as hexstring if (databaseV.size() != s_BYTE_SIZE * 2 || databaseS.size() != s_BYTE_SIZE * 2) _SetVSFields(rI); else { s.SetHexStr(databaseS.c_str()); v.SetHexStr(databaseV.c_str()); } b.SetRand(19 * 8); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16 * 8); // Fill the response packet with the result pkt << uint8(WOW_SUCCESS); // B may be calculated < 32B so we force minimal length to 32B pkt.append(B.AsByteArray(32), 32); // 32 bytes pkt << uint8(1); pkt.append(g.AsByteArray(), 1); pkt << uint8(32); pkt.append(N.AsByteArray(32), 32); pkt.append(s.AsByteArray(), s.GetNumBytes()); // 32 bytes pkt.append(unk3.AsByteArray(16), 16); uint8 securityFlags = 0; pkt << uint8(securityFlags); // security flags (0x0...0x04) if (securityFlags & 0x01) // PIN input { pkt << uint32(0); pkt << uint64(0) << uint64(0); // 16 bytes hash? } if (securityFlags & 0x02) // Matrix input { pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint64(0); } if (securityFlags & 0x04) // Security token input pkt << uint8(1); uint8 secLevel = (*result)[4].GetUInt8(); _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; _localizationName.resize(4); for (int i = 0; i < 4; ++i) _localizationName[i] = ch->country[4-i-1]; sLog->outBasic("'%s:%d' [AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str (), ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName) ); } } } else //no account pkt << (uint8)WOW_FAIL_UNKNOWN_ACCOUNT; } socket().send((char const*)pkt.contents(), pkt.size()); return true; }
// Logon Challenge command handler bool AuthSocket::_HandleLogonChallenge() { #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outDebug(LOG_FILTER_NETWORKIO, "Entering _HandleLogonChallenge"); #endif if (socket().recv_len() < sizeof(sAuthLogonChallenge_C)) return false; ///- Session is closed unless overriden _status = STATUS_CLOSED; // pussywizard: logon flood protection: { TRINITY_GUARD(ACE_Thread_Mutex, LastLoginAttemptMutex); std::string ipaddr = socket().getRemoteAddress(); uint32 currTime = time(NULL); std::map<std::string, uint32>::iterator itr = LastLoginAttemptTimeForIP.find(ipaddr); if (itr != LastLoginAttemptTimeForIP.end() && itr->second >= currTime) { ByteBuffer pkt; pkt << uint8(AUTH_LOGON_CHALLENGE); pkt << uint8(0x00); pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); socket().send((char const*)pkt.contents(), pkt.size()); return true; } if (LastLoginAttemptCleanTime+60 < currTime) { LastLoginAttemptTimeForIP.clear(); LastLoginAttemptCleanTime = currTime; } else LastLoginAttemptTimeForIP[ipaddr] = currTime; } // Read the first 4 bytes (header) to get the length of the remaining of the packet std::vector<uint8> buf; buf.resize(4); socket().recv((char *)&buf[0], 4); EndianConvertPtr<uint16>(&buf[0]); uint16 remaining = ((sAuthLogonChallenge_C *)&buf[0])->size; #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] got header, body is %#04x bytes", remaining); #endif if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (socket().recv_len() < remaining)) return false; //No big fear of memory outage (size is int16, i.e. < 65536) buf.resize(remaining + buf.size() + 1); buf[buf.size() - 1] = 0; sAuthLogonChallenge_C *ch = (sAuthLogonChallenge_C*)&buf[0]; // Read the remaining of the packet socket().recv((char *)&buf[4], remaining); #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] got full packet, %#04x bytes", ch->size); sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I); #endif // BigEndian code, nop in little endian case // size already converted EndianConvertPtr<uint32>(&ch->gamename[0]); EndianConvert(ch->build); EndianConvertPtr<uint32>(&ch->platform[0]); EndianConvertPtr<uint32>(&ch->os[0]); EndianConvertPtr<uint32>(&ch->country[0]); EndianConvert(ch->timezone_bias); EndianConvert(ch->ip); ByteBuffer pkt; _login = (const char*)ch->I; _build = ch->build; _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG)); _os = (const char*)ch->os; if (_os.size() > 4) return false; // Restore string order as its byte order is reversed std::reverse(_os.begin(), _os.end()); pkt << uint8(AUTH_LOGON_CHALLENGE); pkt << uint8(0x00); // Verify that this IP is not in the ip_banned table LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); std::string const& ip_address = socket().getRemoteAddress(); PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_BANNED); stmt->setString(0, ip_address); PreparedQueryResult result = LoginDatabase.Query(stmt); if (result) { pkt << uint8(WOW_FAIL_BANNED); #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outDebug(LOG_FILTER_NETWORKIO, "'%s:%d' [AuthChallenge] Banned ip tries to login!",socket().getRemoteAddress().c_str(), socket().getRemotePort()); #endif } else { // Get the account details from the account table // No SQL injection (prepared statement) stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE); stmt->setString(0, _login); PreparedQueryResult res2 = LoginDatabase.Query(stmt); if (res2) { Field* fields = res2->Fetch(); // If the IP is 'locked', check that the player comes indeed from the correct IP address bool locked = false; if (fields[2].GetUInt8() == 1) // if ip is locked { sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), fields[3].GetCString()); sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] Player address is '%s'", ip_address.c_str()); if (strcmp(fields[4].GetCString(), ip_address.c_str()) != 0) { sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] Account IP differs"); pkt << uint8(WOW_FAIL_LOCKED_ENFORCED); locked = true; } else sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] Account IP matches"); } else { sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); std::string accountCountry = fields[3].GetString(); if (accountCountry.empty() || accountCountry == "00") sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] Account '%s' is not locked to country", _login.c_str()); else if (!accountCountry.empty()) { uint32 ip = inet_addr(ip_address.c_str()); EndianConvertReverse(ip); stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGON_COUNTRY); stmt->setUInt32(0, ip); if (PreparedQueryResult sessionCountryQuery = LoginDatabase.Query(stmt)) { std::string loginCountry = (*sessionCountryQuery)[0].GetString(); sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] Account '%s' is locked to country: '%s' Player country is '%s'", _login.c_str(), accountCountry.c_str(), loginCountry.c_str()); if (loginCountry != accountCountry) { sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] Account country differs."); pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK); locked = true; } else sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] Account country matches"); } else sLog->outDebug(LOG_FILTER_NETWORKIO, "[AuthChallenge] IP2NATION Table empty"); } } if (!locked) { //set expired bans to inactive LoginDatabase.DirectExecute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS)); // If the account is banned, reject the logon attempt stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BANNED); stmt->setUInt32(0, fields[1].GetUInt32()); PreparedQueryResult banresult = LoginDatabase.Query(stmt); if (banresult) { if ((*banresult)[0].GetUInt32() == (*banresult)[1].GetUInt32()) { pkt << uint8(WOW_FAIL_BANNED); sLog->outDebug(LOG_FILTER_NETWORKIO, "'%s:%d' [AuthChallenge] Banned account %s tried to login!", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str ()); } else { pkt << uint8(WOW_FAIL_SUSPENDED); sLog->outDebug(LOG_FILTER_NETWORKIO, "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str ()); } } else { // Get the password from the account table, upper it, and make the SRP6 calculation std::string rI = fields[0].GetString(); // Don't calculate (v, s) if there are already some in the database std::string databaseV = fields[6].GetString(); std::string databaseS = fields[7].GetString(); #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outDebug(LOG_FILTER_NETWORKIO, "database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); #endif // multiply with 2 since bytes are stored as hexstring if (databaseV.size() != s_BYTE_SIZE * 2 || databaseS.size() != s_BYTE_SIZE * 2) _SetVSFields(rI); else { s.SetHexStr(databaseS.c_str()); v.SetHexStr(databaseV.c_str()); } b.SetRand(19 * 8); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16 * 8); // Fill the response packet with the result if (AuthHelper::IsAcceptedClientBuild(_build)) pkt << uint8(WOW_SUCCESS); else pkt << uint8(WOW_FAIL_VERSION_INVALID); // B may be calculated < 32B so we force minimal length to 32B pkt.append(B.AsByteArray(32).get(), 32); // 32 bytes pkt << uint8(1); pkt.append(g.AsByteArray().get(), 1); pkt << uint8(32); pkt.append(N.AsByteArray(32).get(), 32); pkt.append(s.AsByteArray().get(), s.GetNumBytes()); // 32 bytes pkt.append(unk3.AsByteArray(16).get(), 16); uint8 securityFlags = 0; pkt << uint8(securityFlags); // security flags (0x0...0x04) if (securityFlags & 0x01) // PIN input { pkt << uint32(0); pkt << uint64(0) << uint64(0); // 16 bytes hash? } if (securityFlags & 0x02) // Matrix input { pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint64(0); } if (securityFlags & 0x04) // Security token input pkt << uint8(1); uint8 secLevel = fields[5].GetUInt8(); _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; _localizationName.resize(4); for (int i = 0; i < 4; ++i) _localizationName[i] = ch->country[4-i-1]; #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outDebug( LOG_FILTER_NETWORKIO, "'%s:%d' [AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _login.c_str (), ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName) ); #endif ///- All good, await client's proof _status = STATUS_LOGON_PROOF; } } } else //no account pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); } socket().send((char const*)pkt.contents(), pkt.size()); return true; }
static bool HandleGameAccountCreateCommand(ChatHandler* handler, char const* args) { if (!*args) { handler->SendSysMessage(LANG_CMD_SYNTAX); handler->SetSentErrorMessage(true); return false; } std::string bnetAccountName = args; uint32 accountId = Battlenet::AccountMgr::GetId(bnetAccountName); if (!accountId) { handler->PSendSysMessage(LANG_ACCOUNT_NOT_EXIST, bnetAccountName.c_str()); handler->SetSentErrorMessage(true); return false; } uint8 index = Battlenet::AccountMgr::GetMaxIndex(accountId) + 1; std::string accountName = std::to_string(accountId) + '#' + std::to_string(uint32(index)); // Generate random hex string for password, these accounts must not be logged on with GRUNT BigNumber randPassword; randPassword.SetRand(8 * 16); switch (sAccountMgr->CreateAccount(accountName, ByteArrayToHexStr(randPassword.AsByteArray().get(), randPassword.GetNumBytes()), bnetAccountName, accountId, index)) { case AccountOpResult::AOR_OK: handler->PSendSysMessage(LANG_ACCOUNT_CREATED, accountName.c_str()); if (handler->GetSession()) { TC_LOG_INFO("entities.player.character", "Account: %u (IP: %s) Character:[%s] (%s) created Account %s (Email: '%s')", handler->GetSession()->GetAccountId(), handler->GetSession()->GetRemoteAddress().c_str(), handler->GetSession()->GetPlayer()->GetName().c_str(), handler->GetSession()->GetPlayer()->GetGUID().ToString().c_str(), accountName.c_str(), bnetAccountName.c_str()); } break; case AccountOpResult::AOR_NAME_TOO_LONG: handler->SendSysMessage(LANG_ACCOUNT_NAME_TOO_LONG); handler->SetSentErrorMessage(true); return false; case AccountOpResult::AOR_PASS_TOO_LONG: handler->SendSysMessage(LANG_ACCOUNT_PASS_TOO_LONG); handler->SetSentErrorMessage(true); return false; case AccountOpResult::AOR_NAME_ALREADY_EXIST: handler->SendSysMessage(LANG_ACCOUNT_ALREADY_EXIST); handler->SetSentErrorMessage(true); return false; case AccountOpResult::AOR_DB_INTERNAL_ERROR: handler->PSendSysMessage(LANG_ACCOUNT_NOT_CREATED_SQL_ERROR, accountName.c_str()); handler->SetSentErrorMessage(true); return false; default: handler->PSendSysMessage(LANG_ACCOUNT_NOT_CREATED, accountName.c_str()); handler->SetSentErrorMessage(true); return false; } return true; }
void AuthSocket::HandleChallenge() { // No header if(!HasBytes(4)) return; // Check the rest of the packet is complete. uint16 full_size = *(uint16*)&ReceiveBuffer[2]; sLog.outDetail("[AuthChallenge] got header, body is 0x%02X bytes", full_size); if(!HasBytes(full_size)) return; // Copy the data into our cached challenge structure if(full_size > sizeof(sAuthLogonChallenge_C)) { Disconnect(); return; } sLog.outDebug("[AuthChallenge] got full packet."); memcpy(&m_challenge, ReceiveBuffer, full_size + 4); EraseReceiveBytes(full_size+4); // Check client build. if(m_challenge.build > LogonServer::getSingleton().max_build || m_challenge.build < LogonServer::getSingleton().min_build) { SendChallengeError(CE_WRONG_BUILD_NUMBER); return; } // Check for a possible IP ban on this client. #ifdef WIN32 BAN_STATUS ipb = IPBanner::getSingleton().CalculateBanStatus(ConnectedPeer.sin_addr.S_un.S_addr); #else BAN_STATUS ipb = IPBanner::getSingleton().CalculateBanStatus(RetreiveClientIP()); #endif switch(ipb) { case BAN_STATUS_PERMANANT_BAN: SendChallengeError(CE_ACCOUNT_CLOSED); return; case BAN_STATUS_TIME_LEFT_ON_BAN: SendChallengeError(CE_ACCOUNT_FREEZED); return; } // Null-terminate the account string m_challenge.I[m_challenge.I_len] = 0; // Look up the account information string AccountName = (char*)&m_challenge.I; sLog.outDebug("[AuthChallenge] Account Name: \"%s\"", AccountName.c_str()); m_account = AccountMgr::getSingleton().GetAccount(AccountName); if(m_account == 0) { sLog.outDebug("[AuthChallenge] Invalid account."); // Non-existant account SendChallengeError(CE_NO_ACCOUNT); return; } sLog.outDebug("[AuthChallenge] Account banned state = %u", m_account->Banned); // Don't update when IP banned, but update anyway if it's an account ban AccountMgr::getSingleton().UpdateAccountLastIP(m_account->AccountId, RetreiveClientIP()); // Check that the account isn't banned. if(m_account->Banned == 1) { SendChallengeError(CE_ACCOUNT_CLOSED); return; } else if(m_account->Banned > 0) { SendChallengeError(CE_ACCOUNT_FREEZED); return; } // We've passed all initial tests if we're here, lets build the response packet. Sha1Hash I; I.UpdateData((m_account->Username + ":" + m_account->Password)); I.Finalize(); sLog.outDebug("[AuthChallenge] UserPass hash: %X", I.GetDigest()); Sha1Hash sha; uint32 tc = s.GetNumBytes(); sha.UpdateData( s.AsByteArray(), 32 ); sha.UpdateData( I.GetDigest(), 20 ); sha.Finalize(); BigNumber x; x.SetBinary( sha.GetDigest(), sha.GetLength() ); v = g.ModExp(x, N); b.SetRand(152); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk; unk.SetRand(128); uint8 response[200]; uint32 c = 0; response[c] = 0; c += 1; response[c] = 0; c += 1; response[c] = CE_SUCCESS; c += 1; memcpy(&response[c], B.AsByteArray(), 32); c += 32; response[c] = 1; c += 1; response[c] = g.AsByteArray()[0]; c += 1; response[c] = 32; c += 1; memcpy(&response[c], N.AsByteArray(), 32); c += 32; memcpy(&response[c], s.AsByteArray(), s.GetNumBytes()); c += s.GetNumBytes(); memcpy(&response[c], unk.AsByteArray(), 16); c += 16; response[c] = 0; c += 1; SendArray(response, c, FALSE); }
bool AuthSession::_HandleLogonChallenge() { sAuthLogonChallenge_C *challenge = (sAuthLogonChallenge_C*)&_readBuffer; //TC_LOG_DEBUG("server.authserver", "[AuthChallenge] got full packet, %#04x bytes", challenge->size); TC_LOG_DEBUG("server.authserver", "[AuthChallenge] name(%d): '%s'", challenge->I_len, challenge->I); ByteBuffer pkt; _login.assign((const char*)challenge->I, challenge->I_len); _build = challenge->build; _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG)); _os = (const char*)challenge->os; if (_os.size() > 4) return false; // Restore string order as its byte order is reversed std::reverse(_os.begin(), _os.end()); pkt << uint8(AUTH_LOGON_CHALLENGE); pkt << uint8(0x00); // Verify that this IP is not in the ip_banned table LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); std::string const& ipAddress = _socket.remote_endpoint().address().to_string(); unsigned short port = _socket.remote_endpoint().port(); PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_BANNED); stmt->setString(0, ipAddress); PreparedQueryResult result = LoginDatabase.Query(stmt); if (result) { pkt << uint8(WOW_FAIL_BANNED); TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Banned ip tries to login!", ipAddress.c_str(), port); } else { // Get the account details from the account table // No SQL injection (prepared statement) stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE); stmt->setString(0, _login); PreparedQueryResult res2 = LoginDatabase.Query(stmt); if (res2) { Field* fields = res2->Fetch(); // If the IP is 'locked', check that the player comes indeed from the correct IP address bool locked = false; if (fields[2].GetUInt8() == 1) // if ip is locked { TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), fields[4].GetCString()); TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Player address is '%s'", ipAddress.c_str()); if (strcmp(fields[4].GetCString(), ipAddress.c_str()) != 0) { TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account IP differs"); pkt << uint8(WOW_FAIL_LOCKED_ENFORCED); locked = true; } else TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account IP matches"); } else { TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); std::string accountCountry = fields[3].GetString(); if (accountCountry.empty() || accountCountry == "00") TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to country", _login.c_str()); else if (!accountCountry.empty()) { uint32 ip = inet_addr(ipAddress.c_str()); EndianConvertReverse(ip); stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGON_COUNTRY); stmt->setUInt32(0, ip); if (PreparedQueryResult sessionCountryQuery = LoginDatabase.Query(stmt)) { std::string loginCountry = (*sessionCountryQuery)[0].GetString(); TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to country: '%s' Player country is '%s'", _login.c_str(), accountCountry.c_str(), loginCountry.c_str()); if (loginCountry != accountCountry) { TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account country differs."); pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK); locked = true; } else TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account country matches"); } else TC_LOG_DEBUG("server.authserver", "[AuthChallenge] IP2NATION Table empty"); } } if (!locked) { //set expired bans to inactive LoginDatabase.DirectExecute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS)); // If the account is banned, reject the logon attempt stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BANNED); stmt->setUInt32(0, fields[1].GetUInt32()); PreparedQueryResult banresult = LoginDatabase.Query(stmt); if (banresult) { if ((*banresult)[0].GetUInt32() == (*banresult)[1].GetUInt32()) { pkt << uint8(WOW_FAIL_BANNED); TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Banned account %s tried to login!", ipAddress.c_str(), port, _login.c_str()); } else { pkt << uint8(WOW_FAIL_SUSPENDED); TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", ipAddress.c_str(), port, _login.c_str()); } } else { // Get the password from the account table, upper it, and make the SRP6 calculation std::string rI = fields[0].GetString(); // Don't calculate (v, s) if there are already some in the database std::string databaseV = fields[6].GetString(); std::string databaseS = fields[7].GetString(); TC_LOG_DEBUG("network", "database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); // multiply with 2 since bytes are stored as hexstring if (databaseV.size() != BYTE_SIZE * 2 || databaseS.size() != BYTE_SIZE * 2) SetVSFields(rI); else { s.SetHexStr(databaseS.c_str()); v.SetHexStr(databaseV.c_str()); } b.SetRand(19 * 8); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16 * 8); // Fill the response packet with the result if (AuthHelper::IsAcceptedClientBuild(_build)) pkt << uint8(WOW_SUCCESS); else pkt << uint8(WOW_FAIL_VERSION_INVALID); // B may be calculated < 32B so we force minimal length to 32B pkt.append(B.AsByteArray(32).get(), 32); // 32 bytes pkt << uint8(1); pkt.append(g.AsByteArray().get(), 1); pkt << uint8(32); pkt.append(N.AsByteArray(32).get(), 32); pkt.append(s.AsByteArray().get(), s.GetNumBytes()); // 32 bytes pkt.append(unk3.AsByteArray(16).get(), 16); uint8 securityFlags = 0; // Check if token is used _tokenKey = fields[8].GetString(); if (!_tokenKey.empty()) securityFlags = 4; pkt << uint8(securityFlags); // security flags (0x0...0x04) if (securityFlags & 0x01) // PIN input { pkt << uint32(0); pkt << uint64(0) << uint64(0); // 16 bytes hash? } if (securityFlags & 0x02) // Matrix input { pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint64(0); } if (securityFlags & 0x04) // Security token input pkt << uint8(1); uint8 secLevel = fields[5].GetUInt8(); _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; _localizationName.resize(4); for (int i = 0; i < 4; ++i) _localizationName[i] = challenge->country[4 - i - 1]; TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", ipAddress.c_str(), port, _login.c_str(), challenge->country[3], challenge->country[2], challenge->country[1], challenge->country[0], GetLocaleByName(_localizationName) ); } } } else //no account pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT); } std::memcpy(_writeBuffer, (char const*)pkt.contents(), pkt.size()); AsyncWrite(pkt.size()); return true; }
void AuthSocket::HandleChallenge() { // No header if(GetReadBuffer().GetContiguiousBytes() < 4) return; // Check the rest of the packet is complete. uint8 * ReceiveBuffer = (uint8*)GetReadBuffer().GetBufferStart(); #ifdef USING_BIG_ENDIAN uint16 full_size = swap16(*(uint16*)&ReceiveBuffer[2]); #else uint16 full_size = *(uint16*)&ReceiveBuffer[2]; #endif sLog.outDetail("[AuthChallenge] got header, body is 0x%02X bytes", full_size); if(GetReadBuffer().GetSize() < uint32(full_size+4)) return; // Copy the data into our cached challenge structure if(full_size > sizeof(sAuthLogonChallenge_C)) { Disconnect(); return; } sLog.outDebug("[AuthChallenge] got full packet."); //memcpy(&m_challenge, ReceiveBuffer, full_size + 4); //RemoveReadBufferBytes(full_size + 4, true); GetReadBuffer().Read(&m_challenge, full_size + 4); //#ifdef USING_BIG_ENDIAN // uint16 build = swap16(m_challenge.build); // printf("Build: %u\n", build); //#endif // Check client build. #ifdef USING_BIG_ENDIAN uint16 build = swap16(m_challenge.build); #else uint16 build = m_challenge.build; #endif // Check client build. if(build > LogonServer::getSingleton().max_build) { // wtf? SendChallengeError(CE_WRONG_BUILD_NUMBER); return; } if(build < LogonServer::getSingleton().min_build) { // can we patch? char flippedloc[5] = {0,0,0,0,0}; flippedloc[0] = m_challenge.country[3]; flippedloc[1] = m_challenge.country[2]; flippedloc[2] = m_challenge.country[1]; flippedloc[3] = m_challenge.country[0]; m_patch = PatchMgr::getSingleton().FindPatchForClient(build, flippedloc); if(m_patch == NULL) { // could not find a valid patch SendChallengeError(CE_WRONG_BUILD_NUMBER); return; } Log.Debug("Patch", "Selected patch %u%s for client.", m_patch->Version,m_patch->Locality); uint8 response[119] = { 0x00, 0x00, 0x00, 0x72, 0x50, 0xa7, 0xc9, 0x27, 0x4a, 0xfa, 0xb8, 0x77, 0x80, 0x70, 0x22, 0xda, 0xb8, 0x3b, 0x06, 0x50, 0x53, 0x4a, 0x16, 0xe2, 0x65, 0xba, 0xe4, 0x43, 0x6f, 0xe3, 0x29, 0x36, 0x18, 0xe3, 0x45, 0x01, 0x07, 0x20, 0x89, 0x4b, 0x64, 0x5e, 0x89, 0xe1, 0x53, 0x5b, 0xbd, 0xad, 0x5b, 0x8b, 0x29, 0x06, 0x50, 0x53, 0x08, 0x01, 0xb1, 0x8e, 0xbf, 0xbf, 0x5e, 0x8f, 0xab, 0x3c, 0x82, 0x87, 0x2a, 0x3e, 0x9b, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0x32, 0xa3, 0x49, 0x76, 0x5c, 0x5b, 0x35, 0x9a, 0x93, 0x3c, 0x6f, 0x3c, 0x63, 0x6d, 0xc0, 0x00 }; Send(response, 119); return; } // Check for a possible IP ban on this client. BAN_STATUS ipb = IPBanner::getSingleton().CalculateBanStatus(GetRemoteAddress()); switch(ipb) { case BAN_STATUS_PERMANENT_BAN: SendChallengeError(CE_ACCOUNT_CLOSED); return; case BAN_STATUS_TIME_LEFT_ON_BAN: SendChallengeError(CE_ACCOUNT_FREEZED); return; default: break; } // Null-terminate the account string if(m_challenge.I_len >= 0x50) { Disconnect(); return; } m_challenge.I[m_challenge.I_len] = 0; // Clear the shitty hash (for server) string AccountName = (char*)&m_challenge.I; string::size_type i = AccountName.rfind("#"); if( i != string::npos ) { printf("# ACCOUNTNAME!\n"); return; //AccountName.erase( i ); } // Look up the account information sLog.outDebug("[AuthChallenge] Account Name: \"%s\"", AccountName.c_str()); m_account = AccountMgr::getSingleton().GetAccount(AccountName); if(m_account == 0) { sLog.outDebug("[AuthChallenge] Invalid account."); // Non-existant account SendChallengeError(CE_NO_ACCOUNT); return; } sLog.outDebug("[AuthChallenge] Account banned state = %u", m_account->Banned); // Check that the account isn't banned. if(m_account->Banned == 1) { SendChallengeError(CE_ACCOUNT_CLOSED); return; } else if(m_account->Banned > 0) { SendChallengeError(CE_ACCOUNT_FREEZED); return; } // update cached locale if(!m_account->forcedLocale) { char temp[4]; temp[0] = m_challenge.country[3]; temp[1] = m_challenge.country[2]; temp[2] = m_challenge.country[1]; temp[3] = m_challenge.country[0]; *(uint32*)&m_account->Locale[0] = *(uint32*)temp; } Sha1Hash sha; //uint32 tc = s.GetNumBytes(); sha.UpdateData( s.AsByteArray(), 32 ); sha.UpdateData( m_account->SrpHash, 20 ); sha.Finalize(); BigNumber x; x.SetBinary( sha.GetDigest(), sha.GetLength() ); v = g.ModExp(x, N); b.SetRand(152); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk; unk.SetRand(128); uint8 response[200]; uint32 c = 0; response[c] = 0; c += 1; response[c] = 0; c += 1; response[c] = CE_SUCCESS; c += 1; memcpy(&response[c], B.AsByteArray(), 32); c += 32; response[c] = 1; c += 1; response[c] = g.AsByteArray()[0]; c += 1; response[c] = 32; c += 1; memcpy(&response[c], N.AsByteArray(), 32); c += 32; memcpy(&response[c], s.AsByteArray(), s.GetNumBytes()); c += s.GetNumBytes(); memcpy(&response[c], unk.AsByteArray(), 16); c += 16; response[c] = 0; c += 1; Send(response, c); }
/// Logon Challenge command handler bool AuthSocket::_HandleLogonChallenge() { DEBUG_LOG("Entering _HandleLogonChallenge"); if (recv_len() < sizeof(sAuthLogonChallenge_C)) return false; ///- Read the first 4 bytes (header) to get the length of the remaining of the packet std::vector<uint8> buf; buf.resize(4); recv((char*)&buf[0], 4); EndianConvert(*((uint16*)(buf[0]))); uint16 remaining = ((sAuthLogonChallenge_C*)&buf[0])->size; DEBUG_LOG("[AuthChallenge] got header, body is %#04x bytes", remaining); if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (recv_len() < remaining)) return false; // No big fear of memory outage (size is int16, i.e. < 65536) buf.resize(remaining + buf.size() + 1); buf[buf.size() - 1] = 0; sAuthLogonChallenge_C* ch = (sAuthLogonChallenge_C*)&buf[0]; ///- Read the remaining of the packet recv((char*)&buf[4], remaining); DEBUG_LOG("[AuthChallenge] got full packet, %#04x bytes", ch->size); DEBUG_LOG("[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I); // BigEndian code, nop in little endian case // size already converted EndianConvert(*((uint32*)(&ch->gamename[0]))); EndianConvert(ch->build); EndianConvert(*((uint32*)(&ch->platform[0]))); EndianConvert(*((uint32*)(&ch->os[0]))); EndianConvert(*((uint32*)(&ch->country[0]))); EndianConvert(ch->timezone_bias); EndianConvert(ch->ip); ByteBuffer pkt; _login = (const char*)ch->I; _build = ch->build; ///- Normalize account name // utf8ToUpperOnlyLatin(_login); -- client already send account in expected form // Escape the user login to avoid further SQL injection // Memory will be freed on AuthSocket object destruction _safelogin = _login; LoginDatabase.escape_string(_safelogin); pkt << (uint8) CMD_AUTH_LOGON_CHALLENGE; pkt << (uint8) 0x00; ///- Verify that this IP is not in the ip_banned table // No SQL injection possible (paste the IP address as passed by the socket) std::string address = get_remote_address(); LoginDatabase.escape_string(address); QueryResult* result = LoginDatabase.PQuery("SELECT unbandate FROM ip_banned WHERE " // permanent still banned "(unbandate = bandate OR unbandate > UNIX_TIMESTAMP()) AND ip = '%s'", address.c_str()); if (result) { pkt << (uint8)WOW_FAIL_BANNED; BASIC_LOG("[AuthChallenge] Banned ip %s tries to login!", get_remote_address().c_str()); delete result; } else { ///- Get the account details from the account table // No SQL injection (escaped user name) result = LoginDatabase.PQuery("SELECT sha_pass_hash,id,locked,last_ip,gmlevel,v,s FROM account WHERE username = '******'", _safelogin.c_str()); if (result) { ///- If the IP is 'locked', check that the player comes indeed from the correct IP address bool locked = false; if ((*result)[2].GetUInt8() == 1) // if ip is locked { DEBUG_LOG("[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), (*result)[3].GetString()); DEBUG_LOG("[AuthChallenge] Player address is '%s'", get_remote_address().c_str()); if (strcmp((*result)[3].GetString(), get_remote_address().c_str())) { DEBUG_LOG("[AuthChallenge] Account IP differs"); pkt << (uint8) WOW_FAIL_SUSPENDED; locked = true; } else { DEBUG_LOG("[AuthChallenge] Account IP matches"); } } else { DEBUG_LOG("[AuthChallenge] Account '%s' is not locked to ip", _login.c_str()); } if (!locked) { ///- If the account is banned, reject the logon attempt QueryResult* banresult = LoginDatabase.PQuery("SELECT bandate,unbandate FROM account_banned WHERE " "id = %u AND active = 1 AND (unbandate > UNIX_TIMESTAMP() OR unbandate = bandate)", (*result)[1].GetUInt32()); if (banresult) { if ((*banresult)[0].GetUInt64() == (*banresult)[1].GetUInt64()) { pkt << (uint8) WOW_FAIL_BANNED; BASIC_LOG("[AuthChallenge] Banned account %s tries to login!", _login.c_str()); } else { pkt << (uint8) WOW_FAIL_SUSPENDED; BASIC_LOG("[AuthChallenge] Temporarily banned account %s tries to login!", _login.c_str()); } delete banresult; } else { ///- Get the password from the account table, upper it, and make the SRP6 calculation std::string rI = (*result)[0].GetCppString(); ///- Don't calculate (v, s) if there are already some in the database std::string databaseV = (*result)[5].GetCppString(); std::string databaseS = (*result)[6].GetCppString(); DEBUG_LOG("database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str()); // multiply with 2, bytes are stored as hexstring if (databaseV.size() != s_BYTE_SIZE * 2 || databaseS.size() != s_BYTE_SIZE * 2) _SetVSFields(rI); else { s.SetHexStr(databaseS.c_str()); v.SetHexStr(databaseV.c_str()); } b.SetRand(19 * 8); BigNumber gmod = g.ModExp(b, N); B = ((v * 3) + gmod) % N; MANGOS_ASSERT(gmod.GetNumBytes() <= 32); BigNumber unk3; unk3.SetRand(16 * 8); ///- Fill the response packet with the result pkt << uint8(WOW_SUCCESS); // B may be calculated < 32B so we force minimal length to 32B pkt.append(B.AsByteArray(32), 32); // 32 bytes pkt << uint8(1); pkt.append(g.AsByteArray(), 1); pkt << uint8(32); pkt.append(N.AsByteArray(32), 32); pkt.append(s.AsByteArray(), s.GetNumBytes());// 32 bytes pkt.append(unk3.AsByteArray(16), 16); uint8 securityFlags = 0; pkt << uint8(securityFlags); // security flags (0x0...0x04) if (securityFlags & 0x01) // PIN input { pkt << uint32(0); pkt << uint64(0) << uint64(0); // 16 bytes hash? } if (securityFlags & 0x02) // Matrix input { pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint8(0); pkt << uint64(0); } if (securityFlags & 0x04) // Security token input { pkt << uint8(1); } uint8 secLevel = (*result)[4].GetUInt8(); _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR; _localizationName.resize(4); for (int i = 0; i < 4; ++i) _localizationName[i] = ch->country[4 - i - 1]; BASIC_LOG("[AuthChallenge] account %s is using '%c%c%c%c' locale (%u)", _login.c_str(), ch->country[3], ch->country[2], ch->country[1], ch->country[0], GetLocaleByName(_localizationName)); } } delete result; } else // no account { pkt << (uint8) WOW_FAIL_UNKNOWN_ACCOUNT; } } send((char const*)pkt.contents(), pkt.size()); return true; }
void AccountMgr::AddAccount(Field* field) { Account * acct = new Account; Sha1Hash hash; string Username = field[1].GetString(); string Password = field[2].GetString(); string EncryptedPassword = field[3].GetString(); string GMFlags = field[4].GetString(); acct->AccountId = field[0].GetUInt32(); acct->AccountFlags = field[5].GetUInt8(); acct->Banned = field[6].GetUInt32(); if ( (uint32)UNIXTIME > acct->Banned && acct->Banned != 0 && acct->Banned != 1) //1 = perm ban? { //Accounts should be unbanned once the date is past their set expiry date. acct->Banned = 0; //me go boom :( //printf("Account %s's ban has expired.\n",acct->UsernamePtr->c_str()); sLogonSQL->Execute("UPDATE accounts SET banned = 0 WHERE acct=%u",acct->AccountId); } acct->SetGMFlags(GMFlags.c_str()); acct->Locale[0] = 'e'; acct->Locale[1] = 'n'; acct->Locale[2] = 'U'; acct->Locale[3] = 'S'; if(strcmp(field[7].GetString(), "enUS")) { // non-standard language forced memcpy(acct->Locale, field[7].GetString(), 4); acct->forcedLocale = true; } else acct->forcedLocale = false; acct->Muted = field[8].GetUInt32(); if ( (uint32)UNIXTIME > acct->Muted && acct->Muted != 0 && acct->Muted != 1) //1 = perm ban? { //Accounts should be unbanned once the date is past their set expiry date. acct->Muted= 0; //sLog.outDebug("Account %s's mute has expired.",acct->UsernamePtr->c_str()); sLogonSQL->Execute("UPDATE accounts SET muted = 0 WHERE acct=%u",acct->AccountId); } // Convert username/password to uppercase. this is needed ;) ASCENT_TOUPPER(Username); ASCENT_TOUPPER(Password); if( EncryptedPassword.size() > 0 ) { // prefer encrypted passwords over nonencrypted BigNumber bn; bn.SetHexStr( EncryptedPassword.c_str() ); if( bn.GetNumBytes() != 20 ) { printf("Account `%s` has incorrect number of bytes (%u) in encrypted password! Disabling.\n", Username.c_str(), bn.GetNumBytes()); memset(acct->SrpHash, 0, 20); } else { memcpy(acct->SrpHash, bn.AsByteArray(), 20); reverse_array(acct->SrpHash, 20); } } else { // Prehash the I value. hash.UpdateData((Username + ":" + Password)); hash.Finalize(); memcpy(acct->SrpHash, hash.GetDigest(), 20); } AccountDatabase[Username] = acct; }