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}