package net.teamlixo.eggcrack.minecraft; import com.mojang.authlib.GameProfile; import com.mojang.authlib.UserAuthentication; import net.teamlixo.eggcrack.EggCrack; import net.teamlixo.eggcrack.account.Account; import net.teamlixo.eggcrack.account.AuthenticatedAccount; import net.teamlixo.eggcrack.account.output.AttemptedAccount; import net.teamlixo.eggcrack.authentication.AuthenticationException; import net.teamlixo.eggcrack.authentication.configuration.ServiceConfiguration; import net.teamlixo.eggcrack.credential.password.PasswordAuthenticationService; import net.teamlixo.eggcrack.credential.password.PasswordCredential; import net.teamlixo.eggcrack.timer.IntervalTimer; import net.teamlixo.eggcrack.timer.Timer; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.*; import java.util.*; /** * Lowest-level functionality of EggCrack 2.0; all requests are created and managed here. * * Deprecated because of long timeouts. */ @Deprecated public class MojangAuthenticationService extends PasswordAuthenticationService { private static final int MINIMUM_PASSWORD_LEGTH = 6; //Mojang specification private static final InetAddress LOCAL_ADDRESS = InetAddress.getLoopbackAddress(); /** * Authentication factory to use when creating new Mojang authentication instances. */ private AuthenticationFactory authenticationFactory; /** * Interval each proxy may send requests at, in seconds. */ private float interval = 0.05F; private final Map<InetAddress, Timer> intervalMap = new HashMap<InetAddress, Timer>(); private final List<Proxy> unavailableProxies = new ArrayList<Proxy>(); private final ServiceConfiguration.Option<Boolean> checkCape; private final ServiceConfiguration.Option<Boolean> disbleProxies; public MojangAuthenticationService(String name, AuthenticationFactory authenticationFactory) { super(name); this.authenticationFactory = authenticationFactory; this.checkCape = getConfiguration().register( new ServiceConfiguration.Option<Boolean>("Check for cape", Boolean.FALSE) ); this.disbleProxies = getConfiguration().register( new ServiceConfiguration.Option<Boolean>("Proxy timeout", Boolean.TRUE) ); } @Override protected AuthenticatedAccount authenticate(Account account, String password, Proxy proxy) throws AuthenticationException { if (!(account instanceof AttemptedAccount)) throw new AuthenticationException(AuthenticationException.AuthenticationFailure.INVALID_ACCOUNT, "account not properly instantiated"); return authenticateMinecraft(account.getUsername(), password, proxy); } private AuthenticatedAccount authenticateMinecraft(String username, String password, Proxy proxy) throws AuthenticationException { //Ensure username and password are not null. if (username == null) return null; if (password == null) return null; //Step 1: Check username and password for possible corruptions. username = username.trim().replace("\n", "").replace("\r", ""); if (password.equalsIgnoreCase("%user")) password = username; //Little bit of sanitizing. password = password.replace("\n", "").replace("\r", "").trim(); //Make sure the password isn't too short. if (password.length() < MINIMUM_PASSWORD_LEGTH) return null; //Step 2: Check proxy for rate-limiting. InetAddress proxyAddress = proxy.type() == Proxy.Type.DIRECT || proxy.address() == null ? LOCAL_ADDRESS : ((InetSocketAddress)proxy.address()).getAddress(); Timer timer = null; synchronized (intervalMap) { //Not sure if HashMaps are thread-safe. if (disbleProxies.getValue()) { if (!intervalMap.containsKey(proxyAddress)) intervalMap.put(proxyAddress, new IntervalTimer(interval, IntervalTimer.RateWindow.SECOND)); timer = intervalMap.get(proxyAddress); if (proxy.type() != Proxy.Type.DIRECT && !timer.isReady()) { if (!unavailableProxies.contains(proxy)) unavailableProxies.add(proxy); throw new AuthenticationException(AuthenticationException.AuthenticationFailure.BAD_PROXY, "Bad proxy"); } else unavailableProxies.remove(proxy); } } EggCrack.LOGGER.finer("[Authentication] " + username + ": using proxy [type=" + proxy.type().name() + ",address=" + proxyAddress + "]."); //Step 3: Attempt to authenticate the user using the username and password. UserAuthentication userAuthentication = authenticationFactory.createUserAuthentication(proxy); userAuthentication.setUsername(username); userAuthentication.setPassword(password); try { EggCrack.LOGGER.fine("[Authentication] Trying [username=" + username + ", password=" + password + "]."); userAuthentication.logIn(); if (timer != null) timer.next(); GameProfile[] profiles = userAuthentication.getAvailableProfiles(); if (profiles.length <= 0) //Account has no profiles, we logged in but cannot use it. throw new AuthenticationException(AuthenticationException.AuthenticationFailure.NO_PROFILES, "Account has no profiles"); GameProfile profile = userAuthentication.getSelectedProfile(); if (profile == null) profile = profiles[0]; //Select first profile on the account. // See: http://www.mpgh.net/forum/showthread.php?t=1036349 if (checkCape.getValue()) { for (int i = 0; i < 10; i ++) { // Try 10 times to grab a cape. try { String url = String.format( "http://s3.amazonaws.com/MinecraftCloaks/%s.png", URLEncoder.encode(profile.getName(), "UTF8") ); HttpURLConnection urlConnection = (HttpURLConnection) URI.create(url).toURL().openConnection(proxy); if (urlConnection.getResponseCode() / 100 != 2) throw new AuthenticationException( AuthenticationException.AuthenticationFailure.INVALID_ACCOUNT, "Missing cape" ); break; } catch (UnsupportedEncodingException e) { throw new AuthenticationException(AuthenticationException.AuthenticationFailure.INVALID_ACCOUNT, e.getMessage()); } catch (IOException e) { // Retry. } } } return new AuthenticatedAccount( username, //Account username profile.getName(), //Account display name in-game profile.getId(), //Account UUID in-game new PasswordCredential(password) //Account password. ); } catch (com.mojang.authlib.exceptions.AuthenticationException e) { if (timer != null) timer.next(); String errorMessage = e.getMessage(); EggCrack.LOGGER.finer("[Authentication] Attempt [username=" + username + ", password=" + password + "] failed: " + e.getMessage()); if (errorMessage.equals("Invalid credentials. Invalid username or password.")) { //Username or password is not correct. throw new AuthenticationException(AuthenticationException.AuthenticationFailure.INCORRECT_CREDENTIAL, errorMessage); } else if (errorMessage.equals("Invalid credentials.")) { throw new AuthenticationException(AuthenticationException.AuthenticationFailure.REJECTED, errorMessage); } else if (errorMessage.equals("Cannot contact authentication server")) { throw new AuthenticationException(AuthenticationException.AuthenticationFailure.TIMEOUT, errorMessage); } else if (errorMessage.equals("Invalid credentials. Account migrated, use e-mail as username.") || errorMessage.equals("Invalid credentials. Account migrated, use email as username.") || errorMessage.equals("Invalid username")) { throw new AuthenticationException(AuthenticationException.AuthenticationFailure.INVALID_ACCOUNT, errorMessage); } else { EggCrack.LOGGER.warning("[Authentication] Unexpected response: " + e.getMessage()); throw new AuthenticationException(AuthenticationException.AuthenticationFailure.REJECTED, errorMessage); } } catch (NoSuchElementException exception) { throw new AuthenticationException(AuthenticationException.AuthenticationFailure.BAD_PROXY, "Bad proxy"); } } public int unavailableProxies() { return unavailableProxies.size(); } }