256 lines
7.8 KiB
Rust
256 lines
7.8 KiB
Rust
#![allow(incomplete_features)]
|
|
#![feature(const_generics)]
|
|
#![feature(never_type)]
|
|
|
|
mod net;
|
|
|
|
use crate::net::chat::Chat;
|
|
use crate::net::packet_stream::{Client, PacketStream, PacketStreamMaps};
|
|
use crate::net::protocol::state::handshake::Handshake;
|
|
use crate::net::protocol::state::login::Login;
|
|
use crate::net::protocol::state::play::Play;
|
|
use crate::net::protocol::state::status::Status;
|
|
use std::io;
|
|
use std::net::IpAddr;
|
|
use tokio::net::{TcpListener, TcpStream};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> io::Result<()> {
|
|
use clap::{load_yaml, App};
|
|
|
|
let yaml = load_yaml!("cli.yml");
|
|
let args = App::from_yaml(yaml).get_matches();
|
|
let host: IpAddr = args
|
|
.value_of("host")
|
|
.unwrap_or("::")
|
|
.parse()
|
|
.expect("Invalid host IP address.");
|
|
let port: u16 = args
|
|
.value_of("port")
|
|
.unwrap_or("25565")
|
|
.parse()
|
|
.expect("Port must be an integer between 1 an 65535.");
|
|
|
|
let listener = TcpListener::bind((host, port))
|
|
.await
|
|
.unwrap_or_else(|_| panic!("Failed to bind to {}:{}.", host, port));
|
|
|
|
listen(listener).await;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn listen(mut listener: TcpListener) {
|
|
loop {
|
|
let (socket, _) = match listener.accept().await {
|
|
Ok(x) => x,
|
|
Err(e) => {
|
|
eprintln!("Failed to accept client: {:?}", e);
|
|
continue;
|
|
},
|
|
};
|
|
|
|
tokio::spawn(accept_connection(socket));
|
|
}
|
|
}
|
|
|
|
async fn accept_connection(socket: TcpStream) {
|
|
let con = PacketStream::new(Box::new(socket));
|
|
|
|
eprintln!("Client connected.");
|
|
match interact_handshake(con).await {
|
|
Err(err) => {
|
|
eprintln!("Client disconnected with error: {:?}", err);
|
|
},
|
|
Ok(_) => {
|
|
eprintln!("Client disconnected without error.");
|
|
},
|
|
}
|
|
}
|
|
|
|
fn mk_err<A, S: std::borrow::Borrow<str>>(str: S) -> io::Result<A> {
|
|
Err(io::Error::new(io::ErrorKind::Other, str.borrow().to_string()))
|
|
}
|
|
|
|
async fn interact_handshake(mut con: PacketStream<Client, Handshake>) -> io::Result<()> {
|
|
use crate::net::protocol::state::handshake::*;
|
|
|
|
match con.recieve().await? {
|
|
Serverbound::HandshakePkt(handshake) => {
|
|
use HandshakeNextState::*;
|
|
|
|
match handshake.next_state {
|
|
Status => interact_status(con.into_status()).await,
|
|
Login => interact_login(con.into_login()).await,
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
async fn interact_status(mut con: PacketStream<Client, Status>) -> io::Result<()> {
|
|
use crate::net::protocol::state::status::*;
|
|
|
|
loop {
|
|
match con.recieve().await? {
|
|
Serverbound::Request(Request {}) => {
|
|
con.send(&Clientbound::Response(Response {
|
|
data: ResponseData {
|
|
version: ResponseVersion {
|
|
name: "1.16.1".to_string(),
|
|
protocol: 736,
|
|
},
|
|
players: ResponsePlayers {
|
|
max: 255,
|
|
online: 0,
|
|
sample: Vec::new(),
|
|
},
|
|
description: Chat {
|
|
text: "Hello, world!".to_string(),
|
|
},
|
|
favicon: None,
|
|
},
|
|
}))
|
|
.await?;
|
|
},
|
|
Serverbound::Ping(ping) => {
|
|
con.send(&Clientbound::Pong(Pong { payload: ping.payload })).await?;
|
|
|
|
// The status ping is now over so the server ends the connection.
|
|
return Ok(());
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn interact_login(mut con: PacketStream<Client, Login>) -> io::Result<()> {
|
|
use crate::net::protocol::state::login::*;
|
|
|
|
let name = match con.recieve().await? {
|
|
Serverbound::LoginStart(login_start) => login_start.name,
|
|
_ => {
|
|
con.send(&Clientbound::Disconnect(Disconnect {
|
|
reason: Chat {
|
|
text: "Unexpected packet (expected Login Start).".to_string(),
|
|
},
|
|
}))
|
|
.await?;
|
|
return mk_err("Unexpected packet (expected Login Start).");
|
|
},
|
|
};
|
|
|
|
#[cfg(feature = "encryption")]
|
|
{
|
|
use rand::rngs::OsRng;
|
|
use rand::Rng;
|
|
use rsa::{PaddingScheme, RSAPrivateKey};
|
|
|
|
use std::fs::File;
|
|
use std::io::Read;
|
|
let mut public_key = Vec::new();
|
|
File::open("private/pub.der")
|
|
.expect("missing public key")
|
|
.read_to_end(&mut public_key)?;
|
|
|
|
let mut private_key = Vec::new();
|
|
File::open("private/priv.der")
|
|
.expect("missing private key")
|
|
.read_to_end(&mut private_key)?;
|
|
|
|
let key = RSAPrivateKey::from_pkcs1(&private_key).expect("Invalid private key.");
|
|
|
|
let mut verify_token = Vec::new();
|
|
verify_token.resize(4, 0u8);
|
|
OsRng.fill(verify_token.as_mut_slice());
|
|
|
|
let server_id = "";
|
|
|
|
con.send(&Clientbound::EncryptionRequest(EncryptionRequest {
|
|
server_id: server_id.to_string().into_boxed_str(),
|
|
public_key: public_key.clone().into_boxed_slice(),
|
|
verify_token: verify_token.clone().into_boxed_slice(),
|
|
}))
|
|
.await?;
|
|
|
|
let secret = match con.recieve().await? {
|
|
Serverbound::EncryptionResponse(encryption_response) => {
|
|
let token = key
|
|
.decrypt(PaddingScheme::PKCS1v15Encrypt, &encryption_response.verify_token)
|
|
.expect("Failed to decrypt verify token.");
|
|
if token.as_slice() != verify_token.as_slice() {
|
|
return mk_err("Incorrect verify token.");
|
|
}
|
|
|
|
key.decrypt(PaddingScheme::PKCS1v15Encrypt, &encryption_response.shared_secret)
|
|
.expect("Failed to decrypt shared secret.")
|
|
},
|
|
_ => {
|
|
return mk_err("Unexpected packet (expected Encryption Response).");
|
|
},
|
|
};
|
|
|
|
con.enable_encryption(&secret).expect("Failed to set encryption.");
|
|
|
|
#[cfg(feature = "authentication")]
|
|
{
|
|
let server_hash = {
|
|
let server_hash_bytes = {
|
|
use sha1::{Digest, Sha1};
|
|
let mut hasher = Sha1::new();
|
|
hasher.update(server_id.as_bytes());
|
|
hasher.update(&secret);
|
|
hasher.update(&public_key);
|
|
hasher.finalize()
|
|
};
|
|
|
|
format!("{:x}", num_bigint::BigInt::from_signed_bytes_be(&server_hash_bytes))
|
|
};
|
|
|
|
use reqwest::Client;
|
|
|
|
println!(
|
|
"{:?}",
|
|
Client::new()
|
|
.get("https://sessionserver.mojang.com/session/minecraft/hasJoined")
|
|
.header("Content-Type", "application/json")
|
|
.query(&[("username", name.clone()), ("serverId", server_hash.into_boxed_str())])
|
|
.send()
|
|
.await
|
|
.expect("Request failed.")
|
|
.text()
|
|
.await
|
|
.unwrap()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "compression")]
|
|
{
|
|
con.send(&Clientbound::SetCompression(SetCompression {
|
|
threshold: crate::net::serialize::VarInt(64),
|
|
}))
|
|
.await?;
|
|
con.set_compression(Some(64));
|
|
}
|
|
|
|
con.send(&Clientbound::LoginSuccess(LoginSuccess {
|
|
uuid: uuid::Uuid::nil(),
|
|
username: name,
|
|
}))
|
|
.await?;
|
|
|
|
interact_play(con.into_play()).await
|
|
}
|
|
|
|
async fn interact_play(mut con: PacketStream<Client, Play>) -> io::Result<()> {
|
|
use crate::net::protocol::state::play::*;
|
|
|
|
con.send(&Clientbound::Disconnect(Disconnect {
|
|
reason: Chat {
|
|
text: "Goodbye!".to_string(),
|
|
},
|
|
}))
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|