Newer
Older
KeeperJerry_Launcher / LaunchServer / source / response / ResponseThread.java
@KeeperJerry KeeperJerry on 20 Jul 2021 7 KB Попытка в белый лист
package launchserver.response;

import launcher.Launcher;
import launcher.helper.IOHelper;
import launcher.helper.LogHelper;
import launcher.helper.SecurityHelper;
import launcher.helper.VerifyHelper;
import launcher.request.Request.Type;
import launcher.request.RequestException;
import launcher.serialize.HInput;
import launcher.serialize.HOutput;
import launcher.serialize.config.entry.StringConfigEntry;
import launchserver.LaunchServer;
import launchserver.auth.limiter.AuthLimiterIPConfig;
import launchserver.response.auth.AuthResponse;
import launchserver.response.auth.CheckServerResponse;
import launchserver.response.auth.JoinServerResponse;
import launchserver.response.profile.BatchProfileByUsernameResponse;
import launchserver.response.profile.ProfileByUUIDResponse;
import launchserver.response.profile.ProfileByUsernameResponse;
import launchserver.response.update.LauncherResponse;
import launchserver.response.update.UpdateListResponse;
import launchserver.response.update.UpdateResponse;

import java.io.IOException;
import java.math.BigInteger;
import java.net.Socket;
import java.net.SocketException;

public final class ResponseThread implements Runnable
{
    private final LaunchServer server;
    private final String ip;
    private final Socket socket;

    public ResponseThread(LaunchServer server, String ip, Socket socket) throws SocketException
    {
        this.server = server;
        this.ip = ip;
        this.socket = socket;

        // Fix socket flags
        IOHelper.setSocketFlags(socket);
    }

    @Override
    public void run()
    {
        if (AuthLimiterIPConfig.Instance.getAllowIp().stream().noneMatch(s -> s.equals(ip)) && !server.config.authLimitConfig.blockOnConnect && server.config.authLimit && server.config.authLimitConfig.onlyAllowIp)
        {
            if (!server.serverSocketHandler.logConnections) LogHelper.debug("Blocked connection from %s [Not found in Allow List]", ip);
            return;
        }
        else if (AuthLimiterIPConfig.Instance.getBlockIp().stream().anyMatch(s -> s.equals(ip)) && !server.config.authLimitConfig.blockOnConnect && server.config.authLimit && server.config.authLimitConfig.useBlockIp)
        {
            if (!server.serverSocketHandler.logConnections) LogHelper.debug("Blocked connection from %s [Found in Block List]", ip);
            return;
        }
        else
        {
            if (!server.serverSocketHandler.logConnections) LogHelper.debug("Connection from %s", ip);
        }

        // Process connection
        boolean cancelled = false;
        Throwable savedError = null;
        try (HInput input = new HInput(IOHelper.newBufferedInputStream(socket.getInputStream()));
             HOutput output = new HOutput(IOHelper.newBufferedOutStream(socket.getOutputStream())))
        {
            Type type = readHandshake(ip, input, output);
            if (type == null)
            { // Not accepted
                cancelled = true;
                return;
            }

            // Start response
            try
            {
                respond(type, input, output);
            }
            catch (RequestException e)
            {
                LogHelper.subDebug(String.format("#%s Request error: %s", IOHelper.getIP(socket.getRemoteSocketAddress()), e.getMessage()));
                output.writeString(e.getMessage(), 0);
            }
        }
        catch (Throwable exc)
        {
            savedError = exc;
            if (LogHelper.isDebugEnabled()) LogHelper.error(exc);
        }
        finally
        {
            IOHelper.close(socket);
            if (!cancelled)
            {
                server.serverSocketHandler.onDisconnect(IOHelper.getIP(socket.getRemoteSocketAddress()), savedError);
            }
        }
    }

    private Type readHandshake(String ip, HInput input, HOutput output) throws IOException
    {
        boolean legacy = false;

        // Verify magic number
        int magicNumber = input.readInt();
        if (magicNumber != Launcher.PROTOCOL_MAGIC)
        {
            if (magicNumber != Launcher.PROTOCOL_MAGIC - 1)
            { // Previous launcher protocol
                output.writeBoolean(false);
                if (LogHelper.isDebugEnabled()) throw new IOException(String.format("[%s] Protocol magic mismatch", ip));
                return null;
            }
            legacy = true;
        }

        // Verify key modulus
        BigInteger keyModulus = input.readBigInteger(SecurityHelper.RSA_KEY_LENGTH + 1);
        if (!keyModulus.equals(server.privateKey.getModulus()))
        {
            output.writeBoolean(false);
            if (LogHelper.isDebugEnabled()) throw new IOException(String.format("[%s] Key modulus mismatch", ip));
            return null;
        }

        // Read request type
        Type type = Type.read(input);
        if (legacy && type != Type.LAUNCHER)
        {
            output.writeBoolean(false);
            if (LogHelper.isDebugEnabled()) throw new IOException(String.format("[%s] Not LAUNCHER request on legacy protocol", ip));
            return null;
        }
        if (!server.serverSocketHandler.onHandshake(ip, type))
        {
            output.writeBoolean(false);
            return null;
        }

        // Protocol successfully verified
        output.writeBoolean(true);
        output.flush();
        return type;
    }

    private void respond(Type type, HInput input, HOutput output) throws Throwable
    {
        if (server.serverSocketHandler.logConnections)
        {
            LogHelper.info("Connection from %s: %s", ip, type.name());
        }
        else
        {
            LogHelper.subDebug("[%s] Type: %s", ip, type.name());
        }

        // Choose response based on type
        Response response;
        switch (type)
        {
            case PING:
                response = new PingResponse(server, ip, input, output);
                break;
            case AUTH:
                response = new AuthResponse(server, input, output, IOHelper.getIP(socket.getRemoteSocketAddress()));
                break;
            case JOIN_SERVER:
                response = new JoinServerResponse(server, ip, input, output);
                break;
            case CHECK_SERVER:
                response = new CheckServerResponse(server, ip, input, output);
                break;
            case LAUNCHER:
                response = new LauncherResponse(server, ip, input, output);
                break;
            case UPDATE:
                response = new UpdateResponse(server, ip, input, output);
                break;
            case UPDATE_LIST:
                response = new UpdateListResponse(server, ip, input, output);
                break;
            case PROFILE_BY_USERNAME:
                response = new ProfileByUsernameResponse(server, ip, input, output);
                break;
            case PROFILE_BY_UUID:
                response = new ProfileByUUIDResponse(server, ip, input, output);
                break;
            case BATCH_PROFILE_BY_USERNAME:
                response = new BatchProfileByUsernameResponse(server, ip, input, output);
                break;
            case CUSTOM:
                String name = VerifyHelper.verifyIDName(input.readASCII(255));
                response = server.serverSocketHandler.newCustomResponse(name, ip, input, output);
                break;
            default:
                throw new AssertionError("Unsupported request type: " + type.name());
        }

        // Reply
        response.reply();
        LogHelper.subDebug("[%s] Replied", ip);
    }
}