* This method returns one of the cached {@link ScrambledKeyPair ScrambledKeyPairs} for communication with Login Clients. *
* @return a scrambled keypair */ public ScrambledKeyPair getScrambledRSAKeyPair() { return _keyPairs[Rnd.nextInt(10)]; } /** * User name is not case sensitive any more. * @param user * @param password * @param client * @return */ public boolean loginValid(String user, String password, L2LoginClient client)// throws HackingException { boolean ok = false; InetAddress address = client.getConnection().getInetAddress(); // player disconnected meanwhile if ((address == null) || (user == null)) { return false; } Connection con = null; try { MessageDigest md = MessageDigest.getInstance("SHA"); byte[] raw = password.getBytes("UTF-8"); byte[] hash = md.digest(raw); byte[] expected = null; int access = 0; int lastServer = 1; String userIP = null; con = L2DatabaseFactory.getInstance().getConnection(); PreparedStatement statement = con.prepareStatement(USER_INFO_SELECT); statement.setString(1, Long.toString(System.currentTimeMillis())); statement.setString(2, user); ResultSet rset = statement.executeQuery(); if (rset.next()) { expected = Base64.decode(rset.getString("password")); access = rset.getInt("accessLevel"); lastServer = rset.getInt("lastServer"); userIP = rset.getString("userIP"); if (lastServer <= 0) { lastServer = 1; // minServerId is 1 in Interlude } if (Config.DEBUG) { _log.fine("account exists"); } } rset.close(); statement.close(); // if account doesnt exists if (expected == null) { if (Config.AUTO_CREATE_ACCOUNTS) { if ((user.length() >= 2) && (user.length() <= 14)) { statement = con.prepareStatement("INSERT INTO accounts (login,password,lastactive,accessLevel,lastIP) values(?,?,?,?,?)"); statement.setString(1, user); statement.setString(2, Base64.encodeBytes(hash)); statement.setLong(3, System.currentTimeMillis()); statement.setInt(4, 0); statement.setString(5, address.getHostAddress()); statement.execute(); statement.close(); if (Config.LOG_LOGIN_CONTROLLER) { Log.add("'" + user + "' " + address.getHostAddress() + " - OK : AccountCreate", "loginlog"); } _log.info("Created new account for " + user); return true; } if (Config.LOG_LOGIN_CONTROLLER) { Log.add("'" + user + "' " + address.getHostAddress() + " - ERR : ErrCreatingACC", "loginlog"); } _log.warning("Invalid username creation/use attempt: " + user); } else { if (Config.LOG_LOGIN_CONTROLLER) { Log.add("'" + user + "' " + address.getHostAddress() + " - ERR : AccountMissing", "loginlog"); } _log.warning("Account missing for user " + user); FailedLoginAttempt failedAttempt = _hackProtection.get(address); int failedCount; if (failedAttempt == null) { _hackProtection.put(address, new FailedLoginAttempt(address, password)); failedCount = 1; } else { failedAttempt.increaseCounter(); failedCount = failedAttempt.getCount(); } if (failedCount >= Config.LOGIN_TRY_BEFORE_BAN) { _log.info("Banning '" + address.getHostAddress() + "' for " + Config.LOGIN_BLOCK_AFTER_BAN + " seconds due to " + failedCount + " invalid user name attempts"); this.addBanForAddress(address, Config.LOGIN_BLOCK_AFTER_BAN * 1000); } } return false; } // is this account banned? if (access < 0) { if (Config.LOG_LOGIN_CONTROLLER) { Log.add("'" + user + "' " + address.getHostAddress() + " - ERR : AccountBanned", "loginlog"); } client.setAccessLevel(access); return false; } // Check IP if (userIP != null) { if (!isValidIPAddress(userIP)) { // Address is not valid so it's a domain name, get IP try { InetAddress addr = InetAddress.getByName(userIP); userIP = addr.getHostAddress(); } catch (Exception e) { return false; } } if (!address.getHostAddress().equalsIgnoreCase(userIP)) { if (Config.LOG_LOGIN_CONTROLLER) { Log.add("'" + user + "' " + address.getHostAddress() + "/" + userIP + " - ERR : INCORRECT IP", "loginlog"); } return false; } } // check password hash ok = true; for (int i = 0; i < expected.length; i++) { if (hash[i] != expected[i]) { ok = false; break; } } if (ok) { client.setAccessLevel(access); client.setLastServer(lastServer); statement = con.prepareStatement("UPDATE accounts SET lastactive=?, lastIP=? WHERE login=?"); statement.setLong(1, System.currentTimeMillis()); statement.setString(2, address.getHostAddress()); statement.setString(3, user); statement.execute(); statement.close(); } } catch (Exception e) { _log.log(Level.WARNING, "Could not check password:" + e.getMessage(), e); ok = false; } finally { L2DatabaseFactory.close(con); } if (!ok) { if (Config.LOG_LOGIN_CONTROLLER) { Log.add("'" + user + "' " + address.getHostAddress() + " - ERR : LoginFailed", "loginlog"); } FailedLoginAttempt failedAttempt = _hackProtection.get(address); int failedCount; if (failedAttempt == null) { _hackProtection.put(address, new FailedLoginAttempt(address, password)); failedCount = 1; } else { failedAttempt.increaseCounter(password); failedCount = failedAttempt.getCount(); } if (failedCount >= Config.LOGIN_TRY_BEFORE_BAN) { _log.info("Banning '" + address.getHostAddress() + "' for " + Config.LOGIN_BLOCK_AFTER_BAN + " seconds due to " + failedCount + " invalid user/pass attempts"); this.addBanForAddress(address, Config.LOGIN_BLOCK_AFTER_BAN * 1000); } } else { _hackProtection.remove(address); if (Config.LOG_LOGIN_CONTROLLER) { Log.add("'" + user + "' " + address.getHostAddress() + " - OK : LoginOk", "loginlog"); } } return ok; } public boolean isValidIPAddress(String ipAddress) { String[] parts = ipAddress.split("\\."); if (parts.length != 4) { return false; } for (String s : parts) { int i = Integer.parseInt(s); if ((i < 0) || (i > 255)) { return false; } } return true; } class FailedLoginAttempt { // private InetAddress _ipAddress; private int _count; private long _lastAttempTime; private String _lastPassword; public FailedLoginAttempt(InetAddress address, String lastPassword) { // _ipAddress = address; _count = 1; _lastAttempTime = System.currentTimeMillis(); _lastPassword = lastPassword; } public void increaseCounter(String password) { if (!_lastPassword.equals(password)) { // check if theres a long time since last wrong try if ((System.currentTimeMillis() - _lastAttempTime) < (300 * 1000)) { _count++; } else { // restart the status _count = 1; } _lastPassword = password; _lastAttempTime = System.currentTimeMillis(); } else // trying the same password is not brute force { _lastAttempTime = System.currentTimeMillis(); } } public int getCount() { return _count; } public void increaseCounter() { _count++; } } class BanInfo { private final InetAddress _ipAddress; // Expiration private final long _expiration; public BanInfo(InetAddress ipAddress, long expiration) { _ipAddress = ipAddress; _expiration = expiration; } public InetAddress getAddress() { return _ipAddress; } public boolean hasExpired() { return (System.currentTimeMillis() > _expiration) && (_expiration > 0); } } class PurgeThread extends Thread { public PurgeThread() { setName("PurgeThread"); } @Override public void run() { while (!isInterrupted()) { for (L2LoginClient client : _loginServerClients.values()) { if (client == null) { continue; } if ((client.getConnectionStartTime() + LOGIN_TIMEOUT) < System.currentTimeMillis()) { client.close(LoginFailReason.REASON_ACCESS_FAILED); } } try { Thread.sleep(LOGIN_TIMEOUT / 2); } catch (InterruptedException e) { return; } } } } }