package games.strategy.engine.lobby.server.login; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.sql.Timestamp; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import games.strategy.engine.framework.startup.ui.InGameLobbyWatcher; import games.strategy.engine.lobby.server.LobbyServer; import games.strategy.engine.lobby.server.userDB.BadWordController; import games.strategy.engine.lobby.server.userDB.BannedMacController; import games.strategy.engine.lobby.server.userDB.BannedUsernameController; import games.strategy.engine.lobby.server.userDB.DBUserController; import games.strategy.net.ILoginValidator; import games.strategy.util.MD5Crypt; import games.strategy.util.Tuple; import games.strategy.util.Version; public class LobbyLoginValidator implements ILoginValidator { static final String THATS_NOT_A_NICE_NAME = "That's not a nice name."; static final String YOU_HAVE_BEEN_BANNED = "You have been banned from the TripleA lobby."; static final String USERNAME_HAS_BEEN_BANNED = "This username is banned, please create a new one."; static final String UNABLE_TO_OBTAIN_MAC = "Unable to obtain mac address."; static final String INVALID_MAC = "Invalid mac address."; private static final Logger s_logger = Logger.getLogger(LobbyLoginValidator.class.getName()); public static final String LOBBY_VERSION = "LOBBY_VERSION"; public static final String REGISTER_NEW_USER_KEY = "REGISTER_USER"; public static final String ANONYMOUS_LOGIN = "ANONYMOUS_LOGIN"; public static final String LOBBY_WATCHER_LOGIN = "LOBBY_WATCHER_LOGIN"; public static final String LOGIN_KEY = "LOGIN"; public static final String HASHED_PASSWORD_KEY = "HASHEDPWD"; public static final String EMAIL_KEY = "EMAIL"; public static final String SALT_KEY = "SALT"; public LobbyLoginValidator() {} @Override public Map<String, String> getChallengeProperties(final String userName, final SocketAddress remoteAddress) { // we need to give the user the salt key for the username final String password = new DBUserController().getPassword(userName); final Map<String, String> rVal = new HashMap<>(); if (password != null) { rVal.put(SALT_KEY, MD5Crypt.getSalt(MD5Crypt.MAGIC, password)); } return rVal; } @Override public String verifyConnection(final Map<String, String> propertiesSentToClient, final Map<String, String> propertiesReadFromClient, final String clientName, final String clientMac, final SocketAddress remoteAddress) { final String error = verifyConnectionInternal(propertiesReadFromClient, clientName, clientMac); if (error != null) { s_logger.info("Bad login attemp from " + remoteAddress + " for user " + clientName + " error:" + error); AccessLog.failedLogin(clientName, ((InetSocketAddress) remoteAddress).getAddress(), error); } else { s_logger.info("Successful login from:" + remoteAddress + " for user:" + clientName); AccessLog.successfulLogin(clientName, ((InetSocketAddress) remoteAddress).getAddress()); } return error; } private String verifyConnectionInternal(final Map<String, String> propertiesReadFromClient, final String clientName, final String hashedMac) { if (propertiesReadFromClient == null) { return "No Client Properties"; } final String clientVersionString = propertiesReadFromClient.get(LOBBY_VERSION); if (clientVersionString == null) { return "No Client Version"; } final Version clientVersion = new Version(clientVersionString); if (!clientVersion.equals(LobbyServer.LOBBY_VERSION)) { return "Wrong version, we require" + LobbyServer.LOBBY_VERSION.toString() + " but trying to log in with " + clientVersionString; } for (final String s : getBadWords()) { if (clientName.toLowerCase().contains(s.toLowerCase())) { return THATS_NOT_A_NICE_NAME; } } if (hashedMac == null) { return UNABLE_TO_OBTAIN_MAC; } if (hashedMac.length() != 28 || !hashedMac.startsWith(MD5Crypt.MAGIC + "MH$") || !hashedMac.matches("[0-9a-zA-Z$./]+")) { // Must have been tampered with return INVALID_MAC; } final Tuple<Boolean, Timestamp> macBanned = new BannedMacController().isMacBanned(hashedMac); if (macBanned.getFirst()) { return YOU_HAVE_BEEN_BANNED + " " + getBanDurationBreakdown(macBanned.getSecond()); } // test for username ban after testing normal bans, because if it is only a username ban then the user should know // they can change their // name final Tuple<Boolean, Timestamp> usernameBanned = new BannedUsernameController().isUsernameBanned(clientName); if (usernameBanned.getFirst()) { return USERNAME_HAS_BEEN_BANNED + " " + getBanDurationBreakdown(usernameBanned.getSecond()); } if (propertiesReadFromClient.containsKey(REGISTER_NEW_USER_KEY)) { return createUser(propertiesReadFromClient, clientName); } if (propertiesReadFromClient.containsKey(ANONYMOUS_LOGIN)) { return anonymousLogin(propertiesReadFromClient, clientName); } else { return validatePassword(propertiesReadFromClient, clientName); } } static String getBanDurationBreakdown(final Timestamp stamp) { if (stamp == null) { return "Banned Forever"; } final long millis = stamp.getTime() - System.currentTimeMillis(); if (millis < 0) { return "Ban time left: 1 Minute"; } long seconds = Math.max(1, TimeUnit.MILLISECONDS.toSeconds(millis)); final int minutesInSeconds = 60; final int hoursInSeconds = 60 * 60; final int daysInSeconds = 60 * 60 * 24; final long days = seconds / daysInSeconds; seconds -= days * daysInSeconds; final long hours = seconds / hoursInSeconds; seconds -= hours * hoursInSeconds; final long minutes = Math.max(1, seconds / minutesInSeconds); final StringBuilder sb = new StringBuilder(64); sb.append("Ban time left: "); if (days > 0) { sb.append(days); sb.append(" Days "); } if (hours > 0) { sb.append(hours); sb.append(" Hours "); } if (minutes > 0) { sb.append(minutes); sb.append(" Minutes "); } return (sb.toString()); } private List<String> getBadWords() { return new BadWordController().list(); } private static String validatePassword(final Map<String, String> propertiesReadFromClient, final String clientName) { final DBUserController userController = new DBUserController(); if (!userController.login(clientName, propertiesReadFromClient.get(HASHED_PASSWORD_KEY))) { if (userController.doesUserExist(clientName)) { return "Incorrect password"; } else { return "Username does not exist"; } } else { return null; } } private static String anonymousLogin(final Map<String, String> propertiesReadFromClient, final String userName) { if (new DBUserController().doesUserExist(userName)) { return "Can't login anonymously, username already exists"; } // If this is a lobby watcher, use a different set of validation if (propertiesReadFromClient.get(LOBBY_WATCHER_LOGIN) != null && propertiesReadFromClient.get(LOBBY_WATCHER_LOGIN).equals(Boolean.TRUE.toString())) { if (!userName.endsWith(InGameLobbyWatcher.LOBBY_WATCHER_NAME)) { return "Lobby watcher usernames must end with 'lobby_watcher'"; } final String hostName = userName.substring(0, userName.indexOf(InGameLobbyWatcher.LOBBY_WATCHER_NAME)); final String issue = DBUserController.validateUserName(hostName); if (issue != null) { return issue; } } else { final String issue = DBUserController.validateUserName(userName); if (issue != null) { return issue; } } return null; } private static String createUser(final Map<String, String> propertiesReadFromClient, final String userName) { final String email = propertiesReadFromClient.get(EMAIL_KEY); final String hashedPassword = propertiesReadFromClient.get(HASHED_PASSWORD_KEY); final DBUserController controller = new DBUserController(); final String error = controller.validate(userName, email, hashedPassword); if (error != null) { return error; } try { controller.createUser(userName, email, hashedPassword, false); return null; } catch (final IllegalStateException ise) { return ise.getMessage(); } } }