001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package net.tridentsdk.server.packets.login;
019
020import com.google.gson.JsonArray;
021import io.netty.buffer.ByteBuf;
022import net.tridentsdk.server.TridentServer;
023import net.tridentsdk.server.netty.ClientConnection;
024import net.tridentsdk.server.netty.Codec;
025import net.tridentsdk.server.netty.packet.InPacket;
026import net.tridentsdk.server.netty.packet.Packet;
027import net.tridentsdk.server.netty.packet.PacketDirection;
028import net.tridentsdk.server.netty.packet.RSA;
029import net.tridentsdk.server.netty.protocol.Protocol;
030import net.tridentsdk.server.player.TridentPlayer;
031import net.tridentsdk.util.TridentLogger;
032
033import javax.net.ssl.HttpsURLConnection;
034import java.io.BufferedReader;
035import java.io.InputStreamReader;
036import java.net.URL;
037import java.security.KeyPair;
038import java.security.NoSuchAlgorithmException;
039import java.util.UUID;
040
041/**
042 * @author The TridentSDK Team
043 */
044public class PacketLoginInStart extends InPacket {
045    private static final boolean ONLINE_MODE = TridentServer.instance().config().getBoolean("online-mode", true);
046
047    /**
048     * Username of the client to be verified
049     */
050    protected String name;
051
052    @Override
053    public int id() {
054        return 0x00;
055    }
056
057    @Override
058    public Packet decode(ByteBuf buf) {
059        this.name = Codec.readString(buf);
060
061        return this;
062    }
063
064    @Override
065    public PacketDirection direction() {
066        return PacketDirection.IN;
067    }
068
069    /**
070     * Gets the client name
071     *
072     * @return the client name
073     */
074    public String name() {
075        return this.name;
076    }
077
078    @Override
079    public void handleReceived(ClientConnection connection) {
080        // TODO login throttling
081        boolean allow = LoginHandler.getInstance().initLogin(connection.address(), this.name());
082        if (!allow) {
083            connection.sendPacket(new PacketLoginOutDisconnect().setJsonMessage("Server is full"));
084            return;
085        }
086
087        /*
088         * If the client is the local machine, skip the encryption process and proceed to the PLAY stage
089         */
090        if (connection.address().getHostString().equals("127.0.0.1") || !ONLINE_MODE) {
091            UUID id;
092
093            try {
094                URL url = new URL("https://api.mojang.com/profiles/minecraft");
095                HttpsURLConnection c = (HttpsURLConnection) url.openConnection();
096
097                // add request header
098                c.setRequestMethod("POST");
099                c.setRequestProperty("User-Agent", "Mozilla/5.0");
100                c.setRequestProperty("Content-Type", "application/json");
101                c.setDoOutput(true);
102                c.setDoInput(true);
103
104                // write the payload
105                c.getOutputStream().write(String.format("[ \"%s\" ]", name()).getBytes());
106                c.getOutputStream().close();
107
108                int responseCode = c.getResponseCode();
109
110                // if the response isn't 200 OK, logout and inform the client of so
111                if (responseCode != 200) {
112                    connection.sendPacket(new PacketLoginOutDisconnect().setJsonMessage("Unable retrieve UUID"));
113                    return;
114                }
115
116                /*
117                 * Read the response
118                 */
119                StringBuilder sb = new StringBuilder();
120                BufferedReader reader = new BufferedReader(new InputStreamReader(c.getInputStream()));
121                String line;
122
123                while ((line = reader.readLine()) != null) {
124                    sb.append(line);
125                    sb.append('\n');
126                }
127
128                reader.close();
129
130                // parse the response and set the ID
131                JsonArray array = PacketLoginInEncryptionResponse.GSON.fromJson(sb.toString(), JsonArray.class);
132                JsonArray jsonArray = array.getAsJsonArray();
133                if (jsonArray.size() == 0) {
134                    id = UUID.randomUUID();
135                } else {
136                    id = UUID.fromString(PacketLoginInEncryptionResponse.idDash.matcher(
137                            jsonArray.get(0).getAsJsonObject().get("id").getAsString())
138                            .replaceAll("$1-$2-$3-$4-$5"));
139                }
140
141                if (TridentPlayer.getPlayer(id) != null) {
142                    connection.sendPacket(new PacketLoginOutDisconnect().setJsonMessage(
143                            "Player has already logged in"));
144                    return;
145                }
146            } catch (Exception e) {
147                TridentLogger.get().error(e);
148                return;
149            }
150
151            if (TridentServer.WORLD == null) {
152                connection.sendPacket(new PacketLoginOutDisconnect()
153                        .setJsonMessage("{\"text\":\"Disconnected: no world on server\"}"));
154                TridentLogger.get().error("Rejected a client due to not having a map!");
155                return;
156            }
157
158            LoginHandler.getInstance().finish(connection.address());
159            PacketLoginOutSuccess success = new PacketLoginOutSuccess();
160
161            // set values in the packet
162            success.uuid = id.toString();
163            success.username = name();
164            success.connection = connection;
165
166            // send the success packet and spawn the player
167            connection.enableCompression();
168            connection.sendPacket(success);
169            connection.setStage(Protocol.ClientStage.PLAY);
170
171            connection.setUuid(id);
172            TridentPlayer.spawnPlayer(connection, id, name());
173            return;
174        }
175
176        PacketLoginOutEncryptionRequest p = new PacketLoginOutEncryptionRequest();
177
178        // Generate the 4 byte token and update the packet
179        connection.generateToken();
180        p.set("verifyToken", connection.verificationToken());
181
182        try {
183            /* Generate the 1024-bit encryption key specified for the client, only used during the LOGIN stage.
184             * Note: A notchian Minecraft server will have one KeyPair for all clients during the LOGIN stage,
185             * this is flawed as it won't be hard to decrypt the secret which is used as the key for all encryption
186             * after LOGIN therefore generating a keypair for each client is much more secure
187             */
188            KeyPair pair = RSA.generate(1024);
189
190            // Update the packet with the new key
191            p.set("publicKey", pair.getPublic().getEncoded());
192            connection.setLoginKeyPair(pair);
193        } catch (NoSuchAlgorithmException ignored) {
194        }
195
196        // Send the packet to the client
197        connection.sendPacket(p);
198    }
199}