Implement force online/offline mode and offline name listening, create separated packet codec handler

This commit is contained in:
creeper123123321 2021-02-04 12:18:02 -03:00
parent c7a126cf61
commit b41e29e5b7
5 changed files with 255 additions and 184 deletions

View File

@ -1,6 +1,7 @@
package com.github.creeper123123321.viaaas
import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufAllocator
import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInitializer
@ -27,6 +28,7 @@ object FrontChannelInit : ChannelInitializer<Channel>() {
.addLast("frame", FrameCodec())
// "compress"
.addLast("flow-handler", FlowControlHandler())
.addLast("mc", CloudMinecraftCodec())
.addLast(
"handler", CloudMinecraftHandler(
ConnectionData(frontChannel = ch), other = null, frontEnd = true
@ -44,10 +46,34 @@ class BackendInit(val connectionData: ConnectionData) : ChannelInitializer<Chann
.addLast("frame", FrameCodec())
// compress
.addLast("via-codec", CloudViaCodec(user))
.addLast("mc", CloudMinecraftCodec())
.addLast("handler", CloudMinecraftHandler(connectionData, connectionData.frontChannel, frontEnd = false))
}
}
class CloudMinecraftCodec: MessageToMessageCodec<ByteBuf, Packet>() {
override fun encode(ctx: ChannelHandlerContext, msg: Packet, out: MutableList<Any>) {
if (!ctx.channel().isActive) return
val buf = ByteBufAllocator.DEFAULT.buffer()
try {
val handler = ctx.pipeline().get(CloudMinecraftHandler::class.java)
PacketRegistry.encode(msg, buf, handler.data.frontVer!!)
out.add(buf.retain())
} finally {
buf.release()
}
}
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
if (!ctx.channel().isActive || !msg.isReadable) return
val handler = ctx.pipeline().get(CloudMinecraftHandler::class.java)
out.add(PacketRegistry.decode(msg,
handler.data.frontVer ?: 0,
handler.data.state.state, handler.frontEnd))
if (msg.isReadable) throw IllegalStateException("Remaining bytes!!!")
}
}
class CloudCrypto(val cipherDecode: Cipher, var cipherEncode: Cipher) : MessageToMessageCodec<ByteBuf, ByteBuf>() {
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
val i = msg.readerIndex()
@ -130,9 +156,9 @@ class CloudCompressionCodec(val threshold: Int) : MessageToMessageCodec<ByteBuf,
}
class FrameCodec : ByteToMessageCodec<ByteBuf>() {
val badLength = DecoderException("Invalid length!")
val badLength = DecoderException("Invalid length!")
class FrameCodec : ByteToMessageCodec<ByteBuf>() {
override fun decode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
if (!ctx.channel().isActive) {
input.clear() // Ignore, should prevent DoS https://github.com/SpigotMC/BungeeCord/pull/2908

View File

@ -6,7 +6,6 @@ import com.google.gson.Gson
import com.google.gson.JsonObject
import io.ktor.client.request.*
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufAllocator
import io.netty.channel.*
import io.netty.channel.socket.SocketChannel
@ -14,7 +13,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.slf4j.LoggerFactory
import us.myles.ViaVersion.api.protocol.ProtocolVersion
import us.myles.ViaVersion.api.type.Type
import us.myles.ViaVersion.exception.CancelCodecException
import us.myles.ViaVersion.packets.State
@ -26,6 +24,8 @@ import java.security.MessageDigest
import java.security.PrivateKey
import java.security.PublicKey
import java.util.*
import java.util.UUID
import java.util.concurrent.CompletableFuture
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
@ -42,11 +42,6 @@ class ConnectionData(
var frontOnline: Boolean? = null, // todo
var frontName: String? = null,
var backName: String? = null,
var backPublicKey: PublicKey? = null,
var backToken: ByteArray? = null,
var frontToken: ByteArray? = null,
var frontServerId: String? = null,
var backServerId: String? = null,
var frontVer: Int? = null,
var backVer: Int? = null,
) {
@ -58,13 +53,12 @@ class CloudMinecraftHandler(
val data: ConnectionData,
var other: Channel?,
val frontEnd: Boolean
) : SimpleChannelInboundHandler<ByteBuf>() {
) : SimpleChannelInboundHandler<Packet>() {
var remoteAddress: SocketAddress? = null
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
if (ctx.channel().isActive && msg.isReadable) {
data.state.handleMessage(this, ctx, msg)
if (msg.isReadable) throw IllegalStateException("Remaining bytes!!!")
override fun channelRead0(ctx: ChannelHandlerContext, packet: Packet) {
if (ctx.channel().isActive) {
data.state.handlePacket(this, ctx, packet)
}
}
@ -97,9 +91,10 @@ class CloudMinecraftHandler(
}
interface MinecraftConnectionState {
fun handleMessage(
val state: State
fun handlePacket(
handler: CloudMinecraftHandler, ctx: ChannelHandlerContext,
msg: ByteBuf
packet: Packet
)
fun disconnect(handler: CloudMinecraftHandler, msg: String) {
@ -112,19 +107,27 @@ interface MinecraftConnectionState {
}
class HandshakeState : MinecraftConnectionState {
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
val packet = PacketRegistry.decode(
msg,
ProtocolVersion.v1_8, // we still dont know what protocol it is
State.HANDSHAKE,
handler.frontEnd
)
fun connectBack(handler: CloudMinecraftHandler, socketAddr: InetSocketAddress): ChannelFuture {
return Bootstrap()
.handler(BackendInit(handler.data))
.channelFactory(channelSocketFactory())
.group(handler.data.frontChannel.eventLoop())
.option(ChannelOption.IP_TOS, 0x18)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
.connect(socketAddr)
}
override val state: State
get() = State.HANDSHAKE
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
if (packet !is HandshakePacket) throw IllegalArgumentException("Invalid packet!")
handler.data.frontVer = packet.protocolId
when (packet.nextState.ordinal) {
1 -> handler.data.state = StatusState
2 -> handler.data.state = LoginState
2 -> handler.data.state = LoginState()
else -> throw IllegalStateException("Invalid next state")
}
@ -161,22 +164,18 @@ class HandshakeState : MinecraftConnectionState {
if (checkLocalAddress(socketAddr.address)
|| matchesAddress(socketAddr, VIAaaSConfig.blockedBackAddresses)
|| !matchesAddress(socketAddr, VIAaaSConfig.allowedBackAddresses)) {
|| !matchesAddress(socketAddr, VIAaaSConfig.allowedBackAddresses)
) {
throw SecurityException("Not allowed")
}
val bootstrap = Bootstrap()
.handler(BackendInit(handler.data))
.channelFactory(channelSocketFactory())
.group(handler.data.frontChannel.eventLoop())
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
.connect(socketAddr)
val future = connectBack(handler, socketAddr)
bootstrap.addListener {
future.addListener {
if (it.isSuccess) {
mcLogger.info("Connected ${handler.remoteAddress} -> $socketAddr")
val backChan = bootstrap.channel() as SocketChannel
val backChan = future.channel() as SocketChannel
handler.data.backChannel = backChan
handler.other = backChan
@ -205,15 +204,14 @@ class HandshakeState : MinecraftConnectionState {
}
}
object LoginState : MinecraftConnectionState {
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
val packet = PacketRegistry.decode(
msg,
ProtocolVersion.getProtocol(handler.data.frontVer!!),
State.LOGIN,
handler.frontEnd
)
class LoginState : MinecraftConnectionState {
val callbackPlayerId = CompletableFuture<String>()
lateinit var frontToken: ByteArray
lateinit var frontServerId: String
override val state: State
get() = State.LOGIN
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
when (packet) {
is LoginStart -> handleLoginStart(handler, packet)
is CryptoResponse -> handleCryptoResponse(handler, packet)
@ -253,12 +251,7 @@ object LoginState : MinecraftConnectionState {
}
}
fun handleCryptoRequest(handler: CloudMinecraftHandler, cryptoRequest: CryptoRequest) {
val data = handler.data
data.backServerId = cryptoRequest.serverId
data.backPublicKey = cryptoRequest.publicKey
data.backToken = cryptoRequest.token
fun authenticateOnlineFront(frontHandler: CloudMinecraftHandler) {
val id = "VIAaaS" + ByteArray(10).let {
secureRandom.nextBytes(it)
Base64.getEncoder().withoutPadding().encodeToString(it)
@ -269,14 +262,66 @@ object LoginState : MinecraftConnectionState {
secureRandom.nextBytes(it)
it
}
data.frontToken = token
data.frontServerId = id
frontToken = token
frontServerId = id
val cryptoRequest = CryptoRequest()
cryptoRequest.serverId = id
cryptoRequest.publicKey = mcCryptoKey.public
cryptoRequest.token = token
forward(handler, cryptoRequest)
frontHandler.data.frontChannel.writeAndFlush(cryptoRequest)
}
fun handleCryptoRequest(handler: CloudMinecraftHandler, cryptoRequest: CryptoRequest) {
val data = handler.data
val backServerId = cryptoRequest.serverId
val backPublicKey = cryptoRequest.publicKey
val backToken = cryptoRequest.token
if (data.frontOnline == null) {
authenticateOnlineFront(handler)
}
val backKey = ByteArray(16).let {
secureRandom.nextBytes(it)
it
}
val backHash = generateServerHash(backServerId, backKey, backPublicKey)
callbackPlayerId.whenComplete { playerId, e ->
if (e != null) return@whenComplete
val frontHandler = handler.data.frontHandler
GlobalScope.launch(Dispatchers.IO) {
try {
val sessionJoin = viaWebServer.requestSessionJoin(
parseUndashedId(playerId),
handler.data.backName!!,
backHash,
frontHandler.remoteAddress!!, // Frontend handler
backPublicKey
)
val backChan = handler.data.backChannel!!
sessionJoin.whenCompleteAsync({ _, throwable ->
if (throwable != null) {
frontHandler.data.backHandler!!.disconnect("Online mode error: $throwable")
} else {
val cryptoResponse = CryptoResponse()
cryptoResponse.encryptedKey = encryptRsa(backPublicKey, backKey)
cryptoResponse.encryptedToken = encryptRsa(backPublicKey, backToken)
forward(frontHandler, cryptoResponse, true)
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
backChan.pipeline().addBefore("frame", "crypto", CloudCrypto(backAesDe, backAesEn))
}
}, backChan.eventLoop())
} catch (e: Exception) {
frontHandler.disconnect("Online mode error: $e")
}
}
}
}
fun handleCryptoResponse(handler: CloudMinecraftHandler, cryptoResponse: CryptoResponse) {
@ -285,23 +330,16 @@ object LoginState : MinecraftConnectionState {
// RSA token - wat??? why is it encrypted with RSA if it was sent unencrypted?
val decryptedToken = decryptRsa(mcCryptoKey.private, cryptoResponse.encryptedToken)
if (!decryptedToken.contentEquals(handler.data.frontToken!!)) throw IllegalStateException("invalid token!")
if (!decryptedToken.contentEquals(frontToken)) throw IllegalStateException("invalid token!")
val aesEn = mcCfb8(frontKey, Cipher.ENCRYPT_MODE)
val aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE)
handler.data.frontChannel.pipeline().addBefore("frame", "crypto", CloudCrypto(aesDe, aesEn))
generateServerHash(handler.data.frontServerId!!, frontKey, mcCryptoKey.public)
generateServerHash(frontServerId, frontKey, mcCryptoKey.public)
}
val backKey = ByteArray(16).let {
secureRandom.nextBytes(it)
it
}
val backHash = generateServerHash(handler.data.backServerId!!, backKey, handler.data.backPublicKey!!)
handler.data.frontChannel.setAutoRead(false)
GlobalScope.launch(Dispatchers.IO) {
try {
@ -309,45 +347,40 @@ object LoginState : MinecraftConnectionState {
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" +
UrlEscapers.urlFormParameterEscaper().escape(handler.data.frontName!!) +
"&serverId=$frontHash"
)
?: throw IllegalArgumentException("Couldn't authenticate with session servers")
) ?: throw IllegalArgumentException("Couldn't authenticate with session servers")
val sessionJoin = viaWebServer.requestSessionJoin(
fromUndashed(profile.get("id")!!.asString),
handler.data.backName!!,
backHash,
handler.remoteAddress!!, // Frontend handler
handler.data.backPublicKey!!
)
val backChan = handler.other!!
sessionJoin.whenCompleteAsync({ _, throwable ->
if (throwable != null) {
handler.disconnect("Online mode error: $throwable")
} else {
cryptoResponse.encryptedKey = encryptRsa(handler.data.backPublicKey!!, backKey)
cryptoResponse.encryptedToken =
encryptRsa(handler.data.backPublicKey!!, handler.data.backToken!!)
forward(handler, cryptoResponse, true)
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
backChan.pipeline().addBefore("frame", "crypto", CloudCrypto(backAesDe, backAesEn))
}
}, backChan.eventLoop())
val id = profile.get("id")!!.asString
mcLogger.info("Validated front-end session: ${handler.data.frontName} $id")
callbackPlayerId.complete(id)
} catch (e: Exception) {
handler.disconnect("Online mode error: $e")
callbackPlayerId.completeExceptionally(e)
}
handler.data.frontChannel.setAutoRead(true)
}
}
fun handleLoginStart(handler: CloudMinecraftHandler, loginStart: LoginStart) {
if (loginStart.username.length > 16) throw badLength
handler.data.frontName = loginStart.username
handler.data.backName = handler.data.backName ?: handler.data.frontName
loginStart.username = handler.data.backName!!
forward(handler, loginStart)
callbackPlayerId.whenComplete { _, e -> if (e != null) disconnect(handler, "Profile error: $e") }
if (handler.data.frontOnline == false) {
callbackPlayerId.complete(generateOfflinePlayerUuid(handler.data.frontName!!).toString().replace("-", ""))
}
if (handler.data.frontOnline == true) { // forced
authenticateOnlineFront(handler.data.backHandler!!)
callbackPlayerId.whenComplete { _, e ->
if (e == null) forward(handler, loginStart, true)
}
} else {
forward(handler, loginStart)
}
}
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
@ -367,12 +400,12 @@ object LoginState : MinecraftConnectionState {
}
object StatusState : MinecraftConnectionState {
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
val i = msg.readerIndex()
if (Type.VAR_INT.readPrimitive(msg) !in 0..1) throw IllegalArgumentException("Invalid packet id!")
msg.readerIndex(i)
forward(handler, msg.retainedSlice())
msg.clear()
override val state: State
get() = State.STATUS
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
if ((packet as UnknownPacket).id !in 0..1) throw IllegalArgumentException("Invalid packet id!")
forward(handler, packet)
}
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
@ -394,12 +427,12 @@ object StatusState : MinecraftConnectionState {
}
object PlayState : MinecraftConnectionState {
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
val i = msg.readerIndex()
if (Type.VAR_INT.readPrimitive(msg) !in 0..127) throw IllegalArgumentException("Invalid packet id!")
msg.readerIndex(i)
forward(handler, msg.retainedSlice())
msg.clear()
override val state: State
get() = State.PLAY
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
if ((packet as UnknownPacket).id !in 0..127) throw IllegalArgumentException("Invalid packet id!")
forward(handler, packet)
}
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
@ -449,22 +482,17 @@ fun generateServerHash(serverId: String, sharedSecret: ByteArray?, key: PublicKe
private fun forward(handler: CloudMinecraftHandler, packet: Packet, flush: Boolean = false) {
val msg = ByteBufAllocator.DEFAULT.buffer()
try {
PacketRegistry.encode(packet, msg, ProtocolVersion.getProtocol(handler.data.frontVer!!))
forward(handler, msg.retain(), flush)
val ch = handler.other!!
if (flush) {
ch.writeAndFlush(packet, ch.voidPromise())
} else {
ch.write(packet, ch.voidPromise())
}
} finally {
msg.release()
}
}
private fun forward(handler: CloudMinecraftHandler, byteBuf: ByteBuf, flush: Boolean = false) {
val ch = handler.other!!
if (flush) {
ch.writeAndFlush(byteBuf, ch.voidPromise())
} else {
ch.write(byteBuf, ch.voidPromise())
}
}
private fun resolveSrv(address: String, port: Int): Pair<String, Int> {
if (port == 25565) {
try {
@ -509,3 +537,7 @@ private fun matchAddress(addr: String, list: List<String>): Boolean {
list.contains(query)
}
}
// https://github.com/VelocityPowered/Velocity/blob/e3f17eeb245b8d570f16c1f2aff5e7eafb698d5e/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java
fun generateOfflinePlayerUuid(username: String) =
UUID.nameUUIDFromBytes("OfflinePlayer:$username".toByteArray(Charsets.UTF_8))

View File

@ -16,8 +16,8 @@ import kotlin.properties.Delegates
* A mutable object which represents a Minecraft packet data
*/
interface Packet {
fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion)
fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion)
fun decode(byteBuf: ByteBuf, protocolVersion: Int)
fun encode(byteBuf: ByteBuf, protocolVersion: Int)
}
object PacketRegistry {
@ -70,24 +70,24 @@ object PacketRegistry {
)
fun getPacketConstructor(
protocolVersion: ProtocolVersion,
protocolVersion: Int,
state: State,
id: Int,
serverBound: Boolean
): Supplier<out Packet>? {
return entries.firstOrNull {
it.serverBound == serverBound && it.state == state
&& it.versionRange.contains(protocolVersion.version) && it.id == id
&& it.versionRange.contains(protocolVersion) && it.id == id
}?.constructor
}
fun getPacketId(packetClass: Class<out Packet>, protocolVersion: ProtocolVersion): Int? {
fun getPacketId(packetClass: Class<out Packet>, protocolVersion: Int): Int? {
return entries.firstOrNull {
it.versionRange.contains(protocolVersion.version) && it.packetClass == packetClass
it.versionRange.contains(protocolVersion) && it.packetClass == packetClass
}?.id
}
fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion, state: State, serverBound: Boolean): Packet {
fun decode(byteBuf: ByteBuf, protocolVersion: Int, state: State, serverBound: Boolean): Packet {
val packetId = Type.VAR_INT.readPrimitive(byteBuf)
val packet =
getPacketConstructor(protocolVersion, state, packetId, serverBound)?.get() ?: UnknownPacket(packetId)
@ -96,7 +96,7 @@ object PacketRegistry {
return packet
}
fun encode(packet: Packet, byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
fun encode(packet: Packet, byteBuf: ByteBuf, protocolVersion: Int) {
val id = if (packet is UnknownPacket) {
packet.id
} else {
@ -108,13 +108,13 @@ object PacketRegistry {
}
class UnknownPacket(val id: Int) : Packet {
lateinit var content: ByteBuf
lateinit var content: ByteArray
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
content = byteBuf.slice().clear()
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
content = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) }
}
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
byteBuf.writeBytes(content)
}
}
@ -127,14 +127,14 @@ class HandshakePacket : Packet {
var port by Delegates.notNull<Int>()
lateinit var nextState: State
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
protocolId = Type.VAR_INT.readPrimitive(byteBuf)
address = Type.STRING.read(byteBuf)
port = byteBuf.readUnsignedShort()
nextState = State.values()[Type.VAR_INT.readPrimitive(byteBuf)]
}
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
Type.VAR_INT.writePrimitive(byteBuf, protocolId)
Type.STRING.write(byteBuf, address)
byteBuf.writeShort(port)
@ -145,11 +145,11 @@ class HandshakePacket : Packet {
class LoginStart : Packet {
lateinit var username: String
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
username = Type.STRING.read(byteBuf)
}
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
Type.STRING.write(byteBuf, username)
}
}
@ -158,8 +158,8 @@ class CryptoResponse : Packet {
lateinit var encryptedKey: ByteArray
lateinit var encryptedToken: ByteArray
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
if (protocolVersion >= ProtocolVersion.v1_8.version) {
encryptedKey = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
encryptedToken = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
} else {
@ -168,8 +168,8 @@ class CryptoResponse : Packet {
}
}
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
if (protocolVersion >= ProtocolVersion.v1_8.version) {
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, encryptedKey)
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, encryptedToken)
} else {
@ -184,16 +184,16 @@ class CryptoResponse : Packet {
class PluginResponse : Packet {
var id by Delegates.notNull<Int>()
var success by Delegates.notNull<Boolean>()
lateinit var data: ByteBuf
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
lateinit var data: ByteArray
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
id = Type.VAR_INT.readPrimitive(byteBuf)
success = byteBuf.readBoolean()
if (success) {
data = byteBuf.slice().clear()
data = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) }
}
}
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
Type.VAR_INT.writePrimitive(byteBuf, id)
byteBuf.writeBoolean(success)
if (success) {
@ -204,11 +204,11 @@ class PluginResponse : Packet {
class LoginDisconnect : Packet {
lateinit var msg: String
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
msg = Type.STRING.read(byteBuf)
}
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
Type.STRING.write(byteBuf, msg)
}
}
@ -218,9 +218,9 @@ class CryptoRequest : Packet {
lateinit var publicKey: PublicKey
lateinit var token: ByteArray
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
serverId = Type.STRING.read(byteBuf)
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
if (protocolVersion >= ProtocolVersion.v1_8.version) {
publicKey = KeyFactory.getInstance("RSA")
.generatePublic(X509EncodedKeySpec(Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)))
token = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
@ -231,9 +231,9 @@ class CryptoRequest : Packet {
}
}
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
Type.STRING.write(byteBuf, serverId)
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
if (protocolVersion >= ProtocolVersion.v1_8.version) {
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, publicKey.encoded)
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, token)
} else {
@ -250,25 +250,25 @@ class LoginSuccess : Packet {
lateinit var id: UUID
lateinit var username: String
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
id = when {
protocolVersion.version >= ProtocolVersion.v1_16.version -> {
protocolVersion >= ProtocolVersion.v1_16.version -> {
Type.UUID_INT_ARRAY.read(byteBuf)
}
protocolVersion.version >= ProtocolVersion.v1_7_6.version -> {
protocolVersion >= ProtocolVersion.v1_7_6.version -> {
UUID.fromString(Type.STRING.read(byteBuf))
}
else -> fromUndashed(Type.STRING.read(byteBuf))
else -> parseUndashedId(Type.STRING.read(byteBuf))
}
username = Type.STRING.read(byteBuf)
}
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
when {
protocolVersion.version >= ProtocolVersion.v1_16.version -> {
protocolVersion >= ProtocolVersion.v1_16.version -> {
Type.UUID_INT_ARRAY.write(byteBuf, id)
}
protocolVersion.version >= ProtocolVersion.v1_7_6.version -> {
protocolVersion >= ProtocolVersion.v1_7_6.version -> {
Type.STRING.write(byteBuf, id.toString())
}
else -> Type.STRING.write(byteBuf, id.toString().replace("-", ""))
@ -279,11 +279,11 @@ class LoginSuccess : Packet {
class SetCompression : Packet {
var threshold by Delegates.notNull<Int>()
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
threshold = Type.VAR_INT.readPrimitive(byteBuf)
}
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
Type.VAR_INT.writePrimitive(byteBuf, threshold)
}
}
@ -291,14 +291,14 @@ class SetCompression : Packet {
class PluginRequest : Packet {
var id by Delegates.notNull<Int>()
lateinit var channel: String
lateinit var data: ByteBuf
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
lateinit var data: ByteArray
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
id = Type.VAR_INT.readPrimitive(byteBuf)
channel = Type.STRING.read(byteBuf)
data = byteBuf.slice().clear()
data = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) }
}
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
Type.VAR_INT.writePrimitive(byteBuf, id)
Type.STRING.write(byteBuf, channel)
byteBuf.writeBytes(data)

View File

@ -4,20 +4,15 @@ import com.google.common.base.Preconditions
import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import io.ktor.application.*
import io.ktor.client.features.json.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.http.cio.websocket.*
import io.ktor.http.content.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.util.*
import io.ktor.websocket.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.consumeEach
@ -61,7 +56,7 @@ class ViaWebApp {
}
}
} catch (e: Exception) {
e.printStackTrace()
webLogger.info("${call.request.local.remoteHost} (O: ${call.request.origin.remoteHost}) exception: $e")
viaWebServer.onException(this, e)
this.close(CloseReason(CloseReason.Codes.INTERNAL_ERROR, "INTERNAL ERROR"))
} finally {
@ -78,7 +73,7 @@ class ViaWebApp {
}
// https://github.com/VelocityPowered/Velocity/blob/6467335f74a7d1617512a55cc9acef5e109b51ac/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java
fun fromUndashed(string: String): UUID {
fun parseUndashedId(string: String): UUID {
Preconditions.checkArgument(string.length == 32, "Length is incorrect")
return UUID(
java.lang.Long.parseUnsignedLong(string.substring(0, 16), 16),
@ -100,7 +95,7 @@ class WebDashboardServer {
runBlocking {
withContext(Dispatchers.IO) {
httpClient.get<JsonObject?>("https://api.mojang.com/users/profiles/minecraft/$name")
?.get("id")?.asString?.let { fromUndashed(it) }
?.get("id")?.asString?.let { parseUndashedId(it) }
}
}
})
@ -184,9 +179,23 @@ class WebLogin : WebState {
val obj = Gson().fromJson(msg, JsonObject::class.java)
when (obj.getAsJsonPrimitive("action").asString) {
"offline_login" -> {
// todo add some spam check
val username = obj.get("username").asString
val token = UUID.randomUUID()
val uuid = generateOfflinePlayerUuid(username)
webClient.server.loginTokens.put(token, uuid)
webClient.ws.send(
"""{"action": "login_result", "success": true,
| "username": "$username", "uuid": "$uuid", "token": "$token"}""".trimMargin()
)
webLogger.info("${webClient.ws.call.request.local.remoteHost} (O: ${webClient.ws.call.request.origin.remoteHost}) generated a token for offline account $username")
}
"minecraft_id_login" -> {
val username = obj.getAsJsonPrimitive("username").asString
val code = obj.getAsJsonPrimitive("code").asString
val username = obj.get("username").asString
val code = obj.get("code").asString
val check = httpClient.submitForm<JsonObject>(
"https://api.minecraft.id/gateway/verify/${URLEncoder.encode(username, Charsets.UTF_8)}",
@ -200,13 +209,13 @@ class WebLogin : WebState {
webClient.server.loginTokens.put(token, uuid)
webClient.ws.send(
"""{"action": "minecraft_id_result", "success": true,
"""{"action": "login_result", "success": true,
| "username": "$mcIdUser", "uuid": "$uuid", "token": "$token"}""".trimMargin()
)
webLogger.info("${webClient.ws.call.request.local.remoteHost} (O: ${webClient.ws.call.request.origin.remoteHost}) generated a token for account $mcIdUser $uuid")
} else {
webClient.ws.send("""{"action": "minecraft_id_result", "success": false}""")
webClient.ws.send("""{"action": "login_result", "success": false}""")
webLogger.info("${webClient.ws.call.request.local.remoteHost} (O: ${webClient.ws.call.request.origin.remoteHost}) failed to generated a token for account $username")
}
}

View File

@ -219,44 +219,48 @@ function renderActions() {
actions.innerHTML = "";
if (listenVisible) {
if (username != null && mcauth_code != null) {
let p = document.createElement("p");
let add = document.createElement("a");
p.appendChild(add);
add.innerText = "Listen to " + username;
add.href = "#";
add.onclick = () => {
addAction("Listen to " + username, () => {
socket.send(JSON.stringify({
"action": "minecraft_id_login",
"username": username,
"code": mcauth_code}));
mcauth_code = null;
};
actions.appendChild(p);
});
}
let p = document.createElement("p");
let link = document.createElement("a");
p.appendChild(link);
link.innerText = "Listen to username in VIAaaS instance";
link.href = "#";
link.onclick = () => {
let user = prompt("Username (Minecraft.ID is case-sensitive): ", "");
addAction("Listen to premium login in VIAaaS instance", () => {
let user = prompt("Premium username (case-sensitive): ", "");
if (!user) return;
let callbackUrl = new URL(location.origin + location.pathname + "#username=" + encodeURIComponent(user));
location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user) + "?callback=" + encodeURIComponent(callbackUrl);
};
actions.appendChild(p);
location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
+ "?callback=" + encodeURIComponent(callbackUrl);
});
addAction("Listen to offline login in VIAaaS instance", () => {
let user = prompt("Offline username (case-sensitive):", "");
if (!user) return;
socket.send(JSON.stringify({"action": "offline_login", "username": user}));
});
}
}
function addAction(text, onClick) {
let p = document.createElement("p");
let link = document.createElement("a");
p.appendChild(link);
link.innerText = text;
link.href = "#";
link.onclick = onClick;
actions.appendChild(p);
}
function onSocketMsg(event) {
console.log(event.data.toString());
let parsed = JSON.parse(event.data);
if (parsed.action == "ad_minecraft_id_login") {
listenVisible = true;
renderActions();
} else if (parsed.action == "minecraft_id_result") {
} else if (parsed.action == "login_result") {
if (!parsed.success) {
alert("VIAaaS instance couldn't verify account via Minecraft.ID");
alert("VIAaaS instance couldn't verify Minecraft account");
} else {
listen(parsed.token);
saveToken(parsed.token);