eu.jangos.realm.network.handler.RealmAuthHandler.java Source code

Java tutorial

Introduction

Here is the source code for eu.jangos.realm.network.handler.RealmAuthHandler.java

Source

package eu.jangos.realm.network.handler;

/*
 * Copyright 2016 Warkdev.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import eu.jangos.realm.authstep.AuthStep;
import eu.jangos.realm.controller.auth.AccountService;
import eu.jangos.realm.controller.characters.CharacterService;
import eu.jangos.realm.controller.auth.AuthParameterService;
import eu.jangos.realm.controller.world.WorldService;
import eu.jangos.realm.controller.factory.WorldServiceFactory;
import eu.jangos.realm.model.auth.Account;
import eu.jangos.realm.network.opcode.Opcodes;
import eu.jangos.realm.network.opcode.result.AuthEnum;
import eu.jangos.realm.network.packet.AbstractRealmClientPacket;
import eu.jangos.realm.network.packet.AbstractRealmServerPacket;
import eu.jangos.realm.network.packet.client.auth.CMSG_AUTH_SESSION;
import eu.jangos.realm.network.packet.server.auth.SMSG_ADDON_INFO;
import eu.jangos.realm.network.packet.server.auth.SMSG_AUTH_CHALLENGE;
import eu.jangos.realm.network.packet.server.auth.SMSG_AUTH_RESPONSE;
import eu.jangos.realm.utils.AuthUtils;
import eu.jangos.realm.utils.BigNumber;
import eu.jangos.realm.utils.GenericCrypt;
import eu.jangos.realm.utils.VanillaCrypt;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * RealmServerHandler is responsible to handle all the business logic for auth
 * network packets. Managing requests and responses.
 *
 * @author Warkdev
 * @version v0.1 BETA.
 */
public class RealmAuthHandler extends ChannelInboundHandlerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(RealmAuthHandler.class);

    public static final AttributeKey<GenericCrypt> CRYPT = AttributeKey.valueOf("CRYPT");

    public static final AttributeKey<AuthStep> AUTH = AttributeKey.valueOf("AUTH");

    public static final AttributeKey<Account> ACCOUNT = AttributeKey.valueOf("ACCOUNT");

    private static WorldService worldService;
    private final AccountService accountService;
    private static final AuthParameterService parameterService = new AuthParameterService();

    private CMSG_AUTH_SESSION cAuthSession;
    private Account account;

    // Seed is used for auth challenge request.
    private final byte[] seed;

    /**
     * Constructor of RealmServerHandler.
     */
    public RealmAuthHandler() {
        super();
        this.seed = new SecureRandom().generateSeed(4);
        this.accountService = new AccountService();
        worldService = WorldServiceFactory.getInstance();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("A new client is attempting to connect, sending challenge");

        SMSG_AUTH_CHALLENGE response = new SMSG_AUTH_CHALLENGE(Opcodes.SMSG_AUTH_CHALLENGE);
        response.setSeed(seed);

        // Generating a new crypt.
        GenericCrypt crypt = new VanillaCrypt();
        ctx.channel().attr(CRYPT).set(crypt);

        ctx.channel().attr(AUTH).set(AuthStep.STEP_AUTHING);

        ctx.writeAndFlush(response);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        AbstractRealmClientPacket request = (AbstractRealmClientPacket) msg;

        // By default, response is no access for a login activity.
        AbstractRealmServerPacket response = new SMSG_AUTH_RESPONSE(Opcodes.SMSG_AUTH_RESPONSE);

        logger.info(msg.toString());

        switch (request.getOpcode()) {
        case CMSG_AUTH_SESSION:
            if (ctx.channel().attr(AUTH).get() != AuthStep.STEP_AUTHING) {
                logger.error("Client is sending a CMSG_AUTH_SESSION packet again while being authenticating.");
                ctx.close();
                return;
            }

            this.cAuthSession = (CMSG_AUTH_SESSION) request;

            // Checking build number.
            if (this.cAuthSession.getBuild() < Integer.parseInt(parameterService.getParameter("minSupportedBuild"))
                    || this.cAuthSession.getBuild() > Integer
                            .parseInt(parameterService.getParameter("maxSupportedBuild"))) {
                logger.debug("Context: " + ctx.name() + ", account: " + this.cAuthSession.getAccount()
                        + " : Build is not supported.");
                ((SMSG_AUTH_RESPONSE) response).setResult(AuthEnum.AUTH_FAIL_VERSION_INVALID);
                break;
            }

            // Checking account existence.
            if (!this.accountService.checkExistence(this.cAuthSession.getAccount().toUpperCase())) {
                logger.debug("Context: " + ctx.name() + ", account: " + this.cAuthSession.getAccount()
                        + " : Account does not exist.");
                ((SMSG_AUTH_RESPONSE) response).setResult(AuthEnum.AUTH_FAIL_UNKNOWN_ACCOUNT);
                break;
            }

            account = this.accountService.getAccount(this.cAuthSession.getAccount());

            // Checking if this account is the one logging into the authentication server.
            if (!account.getLastIp()
                    .equals(((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress())) {
                logger.debug("Context: " + ctx.name() + ", account: " + this.cAuthSession.getAccount()
                        + " : IP is not the same one than the one used to authenticate.");
                ((SMSG_AUTH_RESPONSE) response).setResult(AuthEnum.AUTH_FAIL_FAIL_NOACCESS);
                break;
            }

            // Checking if account is locked.
            if (account.isLocked()) {
                logger.debug("Context: " + ctx.name() + ", account: " + this.cAuthSession.getAccount()
                        + " : Account is locked.");
                ((SMSG_AUTH_RESPONSE) response).setResult(AuthEnum.AUTH_FAIL_BANNED);
                break;
            }

            // Checking if account is banned -- Includes IP & Account.
            if (this.accountService.isBanned(account,
                    ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress())) {
                logger.debug("Context: " + ctx.name() + ", account: " + this.cAuthSession.getAccount()
                        + " : Account is banned.");
                ((SMSG_AUTH_RESPONSE) response).setResult(AuthEnum.AUTH_FAIL_BANNED);
                break;
            }

            // At this step, we can start making the calculation.
            try {
                BigNumber K = new BigNumber(account.getSessionkey(), 16);

                if (AuthUtils.checkClientDigest(this.cAuthSession.getAccount(), seed, this.cAuthSession.getSeed(),
                        K, this.cAuthSession.getDigest())) {
                    // We are happy that client could login.
                    logger.info("Context: " + ctx.name() + ", account: " + this.cAuthSession.getAccount()
                            + " : Account is logged in.");

                    worldService.addSession(account.getId(), ctx);

                    // Initializing the crypt.
                    ctx.channel().attr(CRYPT).get().init(K.asByteArray());
                    ctx.channel().attr(AUTH).set(AuthStep.STEP_AUTHED);
                    ctx.channel().attr(ACCOUNT).set(account);

                    ((SMSG_AUTH_RESPONSE) response).setResult(AuthEnum.AUTH_SUCCESS);

                    // We send add-on data as well.
                    SMSG_ADDON_INFO packet = new SMSG_ADDON_INFO();
                    packet.setListAddons(((CMSG_AUTH_SESSION) request).getListAddon());
                    ctx.write(packet);
                } else {
                    // Well, the calculation went wrong.
                    logger.info("Context: " + ctx.name() + ", account: " + this.cAuthSession.getAccount()
                            + " : Can't verify the hash.");
                    ((SMSG_AUTH_RESPONSE) response).setResult(AuthEnum.AUTH_FAIL_FAIL_NOACCESS);
                    break;
                }
            } catch (NoSuchAlgorithmException nsa) {
                // Something went wrong, aborting connection.
                logger.info("Context: " + ctx.name() + ", account: " + this.cAuthSession.getAccount()
                        + " : Problem  to find an algorithm.");
                ((SMSG_AUTH_RESPONSE) response).setResult(AuthEnum.AUTH_FAIL_FAIL_NOACCESS);
                break;
            }

            break;
        default:
            logger.error("Packet received, opcode not handled: " + request.getOpcode());
            response = null;
            break;
        }

        if (response != null) {
            ctx.writeAndFlush(response);
        } else {
            // Let pass this to other handlers.
            ctx.fireChannelRead(msg);
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if (this.account != null) {
            worldService.removeSession(account.getId());
        }
    }

    public Account getAccount() {
        return this.account;
    }
}