From 6f6d3c96ca524b1948db1c05e0c28550f2890f76 Mon Sep 17 00:00:00 2001 From: Thinkofdeath Date: Thu, 10 Sep 2015 11:49:41 +0100 Subject: [PATCH] Main part of the protocol complete --- .gitignore | 2 +- Cargo.toml | 8 + openssl/Cargo.toml | 7 + openssl/src/lib.rs | 209 ++++++++++++++++++++++ src/format.rs | 254 ++++++++++++++++++++++++++ src/main.rs | 2 +- src/protocol/mod.rs | 396 +++++++++++++++++++++++++++++++++++++---- src/protocol/mojang.rs | 65 +++++++ src/protocol/packet.rs | 121 +++++++++++-- 9 files changed, 1014 insertions(+), 50 deletions(-) create mode 100644 openssl/Cargo.toml create mode 100644 openssl/src/lib.rs create mode 100644 src/format.rs create mode 100644 src/protocol/mojang.rs diff --git a/.gitignore b/.gitignore index cfe7ef5..7b273b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ *.lock -/target +target/ diff --git a/Cargo.toml b/Cargo.toml index 6074549..eb0514f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,15 @@ authors = [ "Thinkofdeath " ] rustc-serialize = "0.3" glfw = "0.1.0" byteorder = "0.3.13" +hyper = "0.6.13" +serde = "0.6.0" +serde_json = "0.6.0" +flate2 = "0.2.9" [dependencies.steven_gl] path = "./gl" version = "0" + +[dependencies.steven_openssl] +path = "./openssl" +version = "0" diff --git a/openssl/Cargo.toml b/openssl/Cargo.toml new file mode 100644 index 0000000..333c22a --- /dev/null +++ b/openssl/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "steven_openssl" +version = "0.0.1" +authors = [ "Thinkofdeath " ] + +[dependencies] +libc = "*" diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs new file mode 100644 index 0000000..0b54092 --- /dev/null +++ b/openssl/src/lib.rs @@ -0,0 +1,209 @@ +#![allow(dead_code)] +extern crate libc; +use std::mem; +use std::ptr; + +#[repr(C)] +#[allow(non_snake_case)] +struct SHA_CTX { + h0: libc::c_uint, + h1: libc::c_uint, + h2: libc::c_uint, + h3: libc::c_uint, + h4: libc::c_uint, + N1: libc::c_uint, + NH: libc::c_uint, + data: [libc::c_uint; 16], + num: libc::c_uint +} + +#[repr(C)] +struct RSA; + +#[repr(C)] +struct EVP_CIPHER_CTX; + +#[repr(C)] +struct EVP_CIPHER; + +#[repr(C)] +struct ENGINE; + +const RSA_PKCS1_PADDING : libc::c_int = 1; + +#[link(name = "crypto")] +extern { + fn SHA1_Init(c: *mut SHA_CTX) -> libc::c_int; + fn SHA1_Update(c: *mut SHA_CTX, data: *const u8, len: libc::size_t) -> libc::c_int; + fn SHA1_Final(md: *const u8, c: *mut SHA_CTX) -> libc::c_int; + + fn d2i_RSA_PUBKEY(a: *mut *mut RSA, data: *const *const u8, len: libc::c_int) -> *mut RSA; + fn RSA_free(rsa: *mut RSA); + fn RSA_size(rsa: *mut RSA) -> libc::c_int; + fn RSA_public_encrypt(flen: libc::c_int, from: *const u8, to: *mut u8, rsa: *mut RSA, padding: libc::c_int) -> libc::c_int; + + fn RAND_bytes(buf: *mut u8, len: libc::c_int) -> libc::c_int; + + fn EVP_CIPHER_CTX_init(a: *mut EVP_CIPHER_CTX); + fn EVP_EncryptInit_ex(ctx: *mut EVP_CIPHER_CTX, cipher: *const EVP_CIPHER, engine: *mut ENGINE, key: *const u8, iv: *const u8) -> libc::c_int; + fn EVP_DecryptInit_ex(ctx: *mut EVP_CIPHER_CTX, cipher: *const EVP_CIPHER, engine: *mut ENGINE, key: *const u8, iv: *const u8) -> libc::c_int; + fn EVP_EncryptUpdate(ctx: *mut EVP_CIPHER_CTX, out: *mut u8, outl: *mut libc::c_int, input: *const u8, inl: libc::c_int) -> libc::c_int; + fn EVP_DecryptUpdate(ctx: *mut EVP_CIPHER_CTX, out: *mut u8, outl: *mut libc::c_int, input: *const u8, inl: libc::c_int) -> libc::c_int; + fn EVP_aes_128_cfb8() -> *const EVP_CIPHER; + fn EVP_CIPHER_block_size(cipher: *const EVP_CIPHER) -> libc::c_int; + fn EVP_CIPHER_CTX_free(ctx: *mut EVP_CIPHER_CTX); + fn EVP_CIPHER_CTX_cleanup(ctx: *mut EVP_CIPHER_CTX) -> libc::c_int; + fn EVP_CIPHER_CTX_new() -> *mut EVP_CIPHER_CTX; +} + +pub struct EVPCipher { + internal: *mut EVP_CIPHER_CTX, + + block_size: usize, +} + +impl EVPCipher { + pub fn new(key: &Vec, iv: &Vec, decrypt: bool) -> EVPCipher { + unsafe { + let e = EVPCipher { + internal: EVP_CIPHER_CTX_new(), + block_size: EVP_CIPHER_block_size(EVP_aes_128_cfb8()) as usize, + }; + EVP_CIPHER_CTX_init(e.internal); + if decrypt { + if EVP_DecryptInit_ex(e.internal, EVP_aes_128_cfb8(), ptr::null_mut(), key.as_ptr(), iv.as_ptr()) == 0 { + panic!("Init error"); + } + } else { + if EVP_EncryptInit_ex(e.internal, EVP_aes_128_cfb8(), ptr::null_mut(), key.as_ptr(), iv.as_ptr()) == 0 { + panic!("Init error"); + } + } + e + } + } + + pub fn decrypt(&mut self, data: &[u8]) -> Vec { + unsafe { + let mut out = Vec::with_capacity(data.len()); + let mut out_len: libc::c_int = 0; + if EVP_DecryptUpdate(self.internal, out.as_mut_ptr(), &mut out_len, data.as_ptr(), data.len() as libc::c_int) == 0 { + panic!("Decrypt error") + } + out.set_len(out_len as usize); + out + } + } + + pub fn encrypt(&mut self, data: &[u8]) -> Vec { + unsafe { + let mut out = Vec::with_capacity(data.len()); + let mut out_len: libc::c_int = 0; + if EVP_EncryptUpdate(self.internal, out.as_mut_ptr(), &mut out_len, data.as_ptr(), data.len() as libc::c_int) == 0 { + panic!("Encrypt error") + } + out.set_len(out_len as usize); + out + } + } +} + + +impl Drop for EVPCipher { + fn drop(&mut self) { + unsafe { + EVP_CIPHER_CTX_cleanup(self.internal); + EVP_CIPHER_CTX_free(self.internal); + } + } +} + +pub fn gen_random(len: usize) -> Vec { + unsafe { + let mut data = Vec::with_capacity(len); + data.set_len(len); + RAND_bytes(data.as_mut_ptr(), len as libc::c_int); + data + } +} + + +pub struct PublicKey { + rsa: *mut RSA +} + +impl PublicKey { + pub fn new(data: &Vec) -> PublicKey { + unsafe { + let cert = d2i_RSA_PUBKEY(ptr::null_mut(), &data.as_ptr(), data.len() as libc::c_int); + if cert.is_null() { + panic!("Error"); + } + PublicKey { rsa: cert } + } + } + + pub fn encrypt(&mut self, data: &Vec) -> Vec { + unsafe { + let size = RSA_size(self.rsa) as usize; + let mut out = Vec::with_capacity(size); + out.set_len(size); + RSA_public_encrypt(data.len() as libc::c_int, data.as_ptr(), out.as_mut_ptr(), self.rsa, RSA_PKCS1_PADDING); + out + } + } +} + +impl Drop for PublicKey { + fn drop(&mut self) { + unsafe { + RSA_free(self.rsa); + } + } +} + +pub struct SHA1 { + internal: SHA_CTX, +} + +impl SHA1 { + pub fn new() -> SHA1 { + unsafe { + let mut s : SHA1 = mem::uninitialized(); + if SHA1_Init(&mut s.internal) == 0 { + panic!("error init sha1"); + } + s + } + } + + pub fn update(&mut self, data: &[u8]) { + unsafe { + if SHA1_Update(&mut self.internal, data.as_ptr(), data.len() as libc::size_t) == 0 { + panic!("sha1 error"); + } + } + } + + pub fn bytes(mut self) -> Vec { + unsafe { + let mut dst = Vec::with_capacity(20); + dst.set_len(20); + if SHA1_Final(dst.as_mut_ptr(), &mut self.internal) == 0 { + panic!("sha1 error"); + } + dst + } + } +} + +#[cfg(test)] +mod test { + use super::{SHA1}; + #[test] + fn test_sha1() { + let mut sha1 = SHA1::new(); + sha1.update(b"Hello world"); + assert!(sha1.bytes().iter().map(|b| format!("{:02X}", b)).collect::>().connect("") == "7B502C3A1F48C8609AE212CDFB639DEE39673F5E"); + } +} diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..a762cc0 --- /dev/null +++ b/src/format.rs @@ -0,0 +1,254 @@ +extern crate serde_json; + +use std::fmt; + +#[derive(Debug)] +pub enum Component { + Text(TextComponent), + None +} + +impl Component { + pub fn from_value(v: &serde_json::Value) -> Self { + let modifier = Modifier::from_value(v); + if let Some(val) = v.as_string() { + Component::Text(TextComponent{text: val.to_owned(), modifier: modifier}) + } else if v.find("text").is_some(){ + Component::Text(TextComponent::from_value(v, modifier)) + } else { + Component::None + } + } + + pub fn to_value(&self) -> serde_json::Value { + unimplemented!() + } +} + +impl fmt::Display for Component { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Component::Text(ref txt) => write!(f, "{}", txt), + Component::None => Result::Ok(()), + } + } +} + +impl Default for Component { + fn default() -> Self { + Component::None + } +} + +#[derive(Debug)] +pub struct Modifier { + pub extra: Option>, + pub bold: Option, + pub italic: Option, + pub underlined: Option, + pub strikethrough: Option, + pub obfuscated: Option, + pub color: Option, + + // click_event + // hover_event + // insertion +} + +impl Modifier { + pub fn from_value(v: &serde_json::Value) -> Self { + let mut m = Modifier { + bold: v.find("bold").map_or(Option::None, |v| v.as_boolean()), + italic: v.find("italic").map_or(Option::None, |v| v.as_boolean()), + underlined: v.find("underlined").map_or(Option::None, |v| v.as_boolean()), + strikethrough: v.find("strikethrough").map_or(Option::None, |v| v.as_boolean()), + obfuscated: v.find("obfuscated").map_or(Option::None, |v| v.as_boolean()), + color: v.find("color").map_or(Option::None, |v| v.as_string()).map(|v| Color::from_string(&v.to_owned())), + extra: Option::None, + }; + if let Some(extra) = v.find("extra") { + if let Some(data) = extra.as_array() { + let mut ex = Vec::new(); + for e in data { + ex.push(Component::from_value(e)); + } + m.extra = Some(ex); + } + } + m + } + + pub fn to_value(&self) -> serde_json::Value { + unimplemented!() + } +} + +#[derive(Debug)] +pub struct TextComponent { + pub text: String, + pub modifier: Modifier, +} + +impl TextComponent { + pub fn from_value(v: &serde_json::Value, modifier: Modifier) -> Self { + TextComponent { + text: v.find("text").unwrap().as_string().unwrap_or("").to_owned(), + modifier: modifier, + } + } + + pub fn to_value(&self) -> serde_json::Value { + unimplemented!() + } +} + +impl fmt::Display for TextComponent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "{}", self.text)); + if let Some(ref extra) = self.modifier.extra { + for c in extra { + try!(write!(f, "{}", c)); + } + } + Result::Ok(()) + } +} + +#[derive(Debug)] +pub enum Color { + Black, + DarkBlue, + DarkGreen, + DarkAqua, + DarkRed, + DarkPurple, + Gold, + Gray, + DarkGray, + Blue, + Green, + Aqua, + Red, + LightPurple, + Yellow, + White, + RGB(u8, u8, u8) +} + +impl fmt::Display for Color { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} + +impl Color { + fn from_string(val: &String) -> Self { + match val.as_ref() { + "black" => Color::Black, + "dark_blue" => Color::DarkBlue, + "dark_green" => Color::DarkGreen, + "dark_aqua" => Color::DarkAqua, + "dark_red" => Color::DarkRed, + "dark_purple" => Color::DarkPurple, + "gold" => Color::Gold, + "gray" => Color::Gray, + "dark_gray" => Color::DarkGray, + "blue" => Color::Blue, + "green" => Color::Green, + "aqua" => Color::Aqua, + "red" => Color::Red, + "light_purple" => Color::LightPurple, + "yellow" => Color::Yellow, + "white" => Color::White, + val if val.len() == 7 && val.as_bytes()[0] == b'#' => { + let r = match u8::from_str_radix(&val[1..3], 16) { + Ok(r) => r, + Err(_) => return Color::White, + }; + let g = match u8::from_str_radix(&val[3..5], 16) { + Ok(g) => g, + Err(_) => return Color::White, + }; + let b = match u8::from_str_radix(&val[5..7], 16) { + Ok(b) => b, + Err(_) => return Color::White, + }; + Color::RGB( + r, + g, + b + ) + } + _ => Color::White, + } + } + + fn to_string(&self) -> String { + match *self { + Color::Black => "black".to_owned(), + Color::DarkBlue => "dark_blue".to_owned(), + Color::DarkGreen => "dark_green".to_owned(), + Color::DarkAqua => "dark_aqua".to_owned(), + Color::DarkRed => "dark_red".to_owned(), + Color::DarkPurple => "dark_purple".to_owned(), + Color::Gold => "gold".to_owned(), + Color::Gray => "gray".to_owned(), + Color::DarkGray => "dark_gray".to_owned(), + Color::Blue => "blue".to_owned(), + Color::Green => "green".to_owned(), + Color::Aqua => "aqua".to_owned(), + Color::Red => "red".to_owned(), + Color::LightPurple => "light_purple".to_owned(), + Color::Yellow => "yellow".to_owned(), + Color::White => "white".to_owned(), + Color::RGB(r, g, b) => format!("#{:02X}{:02X}{:02X}", r, g, b), + } + } + + #[allow(dead_code)] + fn to_rgb(&self) -> (u8, u8, u8) { + match *self { + Color::Black =>(0, 0, 0), + Color::DarkBlue =>(0, 0, 170), + Color::DarkGreen =>(0, 170, 0), + Color::DarkAqua =>(0, 170, 170), + Color::DarkRed =>(170, 0, 0), + Color::DarkPurple =>(170, 0, 170), + Color::Gold =>(255, 170, 0), + Color::Gray =>(170, 170, 170), + Color::DarkGray =>(85, 85, 85), + Color::Blue =>(85, 85, 255), + Color::Green =>(85, 255, 85), + Color::Aqua =>(85, 255, 255), + Color::Red =>(255, 85, 85), + Color::LightPurple =>(255, 85, 255), + Color::Yellow =>(255, 255, 85), + Color::White =>(255, 255, 255), + Color::RGB(r, g, b) => (r, g, b), + } + } +} + +#[test] +fn test_color_from() { + let test = Color::from_string(&"#FF0000".to_owned()); + match test { + Color::RGB(r, g, b) => assert!(r == 255 && g == 0 && b == 0), + _ => panic!("Wrong type"), + } + let test = Color::from_string(&"#123456".to_owned()); + match test { + Color::RGB(r, g, b) => assert!(r == 0x12 && g == 0x34 && b == 0x56), + _ => panic!("Wrong type"), + } + let test = Color::from_string(&"red".to_owned()); + match test { + Color::Red => {}, + _ => panic!("Wrong type"), + } + let test = Color::from_string(&"dark_blue".to_owned()); + match test { + Color::DarkBlue => {}, + _ => panic!("Wrong type"), + } +} diff --git a/src/main.rs b/src/main.rs index d2e2ec5..561f09e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ extern crate glfw; -extern crate byteorder; pub mod bit; pub mod protocol; +pub mod format; mod gl; use glfw::{Action, Context, Key}; diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 4bba560..ace9268 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,12 +1,20 @@ #![allow(dead_code)] extern crate byteorder; +extern crate hyper; +extern crate steven_openssl as openssl; +extern crate flate2; +extern crate serde_json; +pub mod mojang; + +use format; use std::default; use std::net::TcpStream; use std::io; use std::io::{Write, Read}; use std::convert; -use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; +use self::byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; +use self::flate2::read::{ZlibDecoder, ZlibEncoder}; /// Helper macro for defining packets #[macro_export] @@ -14,13 +22,12 @@ macro_rules! state_packets { ($($state:ident $stateName:ident { $($dir:ident $dirName:ident { $($name:ident => $id:expr { - $($field:ident: $field_type:ident),+ - }),* + $($field:ident: $field_type:ty = $(when ($cond:expr))*, )+ + })* })+ })+) => { use protocol::*; use std::io; - use protocol::{Serializable}; pub enum Packet { $( @@ -36,9 +43,10 @@ macro_rules! state_packets { pub mod $state { $( pub mod $dir { + #![allow(unused_imports)] use protocol::*; use std::io; - use protocol::{Serializable}; + use format; $( #[derive(Default)] @@ -52,7 +60,9 @@ macro_rules! state_packets { fn write(self, buf: &mut Vec) -> Result<(), io::Error> { $( - try!(self.$field.write_to(buf)); + if true $(&& ($cond(&self)))* { + try!(self.$field.write_to(buf)); + } )+ Result::Ok(()) @@ -76,9 +86,12 @@ macro_rules! state_packets { match id { $( $id => { - let mut packet : $state::$dir::$name = $state::$dir::$name::default(); + use self::$state::$dir::$name; + let mut packet : $name = $name::default(); $( - packet.$field = try!($field_type::read_from(&mut buf)); + if true $(&& ($cond(&packet)))* { + packet.$field = try!(Serializable::read_from(&mut buf)); + } )+ Result::Ok(Option::Some(Packet::$name(packet))) }, @@ -97,11 +110,23 @@ macro_rules! state_packets { pub mod packet; -trait Serializable { +pub trait Serializable { fn read_from(buf: &mut io::Read) -> Result; fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error>; } +impl Serializable for Option where T : Serializable { + fn read_from(buf: &mut io::Read) -> Result, io::Error> { + Result::Ok(Some(try!(T::read_from(buf)))) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + if self.is_some() { + try!(self.as_ref().unwrap().write_to(buf)); + } + Result::Ok(()) + } +} + impl Serializable for String { fn read_from(buf: &mut io::Read) -> Result { let len = try!(VarInt::read_from(buf)).0; @@ -117,17 +142,90 @@ impl Serializable for String { } } -impl Serializable for Empty { - fn read_from(buf: &mut io::Read) -> Result { - Result::Ok(Empty) +impl Serializable for format::Component { + fn read_from(buf: &mut io::Read) -> Result { + let len = try!(VarInt::read_from(buf)).0; + let mut ret = String::new(); + try!(buf.take(len as u64).read_to_string(&mut ret)); + let val : serde_json::Value = serde_json::from_str(&ret[..]).unwrap(); + Result::Ok(Self::from_value(&val)) } fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + let val = serde_json::to_string(&self.to_value()).unwrap(); + let bytes = val.as_bytes(); + try!(VarInt(bytes.len() as i32).write_to(buf)); + try!(buf.write_all(bytes)); Result::Ok(()) } } -impl Default for Empty { - fn default() -> Empty { Empty } +pub struct Position(u64); + +impl Position { + fn new(x: i32, y: i32, z: i32) -> Position { + Position( + (((x as u64) & 0x3FFFFFF) << 38) | + (((y as u64) & 0xFFF) << 26) | + ((z as u64) & 0x3FFFFFF) + ) + } + + fn get_x(&self) -> i32 { + ((self.0 as i64) >> 38) as i32 + } + + fn get_y(&self) -> i32 { + (((self.0 as i64) >> 26) & 0xFFF) as i32 + } + + fn get_z(&self) -> i32 { + ((self.0 as i64) << 38 >> 38) as i32 + } +} + +impl Default for Position { + fn default() -> Position { + Position(0) + } +} + +impl Serializable for Position { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(Position(try!(buf.read_u64::()))) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_u64::(self.0)); + Result::Ok(()) + } +} + +impl Serializable for () { + fn read_from(_: &mut io::Read) -> Result<(), io::Error> { + Result::Ok(()) + } + fn write_to(&self, _: &mut io::Write) -> Result<(), io::Error> { + Result::Ok(()) + } +} + +impl Serializable for bool { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(try!(buf.read_u8()) != 0) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_u8(if *self { 1 } else { 0 })); + Result::Ok(()) + } +} + +impl Serializable for i16 { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(try!(buf.read_i16::())) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_i16::(*self)); + Result::Ok(()) + } } impl Serializable for i32 { @@ -150,6 +248,16 @@ impl Serializable for i64 { } } +impl Serializable for u8 { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(try!(buf.read_u8())) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_u8(*self)); + Result::Ok(()) + } +} + impl Serializable for u16 { fn read_from(buf: &mut io::Read) -> Result { Result::Ok(try!(buf.read_u16::())) @@ -160,10 +268,60 @@ impl Serializable for u16 { } } +pub trait Lengthable : Serializable + Into + From + Copy + Default {} + +pub struct LenPrefixed { + len: L, + pub data: Vec +} + +impl LenPrefixed { + fn new(data: Vec) -> LenPrefixed { + return LenPrefixed { + len: Default::default(), + data: data, + } + } +} + +impl Serializable for LenPrefixed { + fn read_from(buf: &mut io::Read) -> Result, io::Error> { + let len_data : L = try!(Serializable::read_from(buf)); + let len : usize = len_data.into(); + let mut data : Vec = Vec::with_capacity(len); + for _ in 0 .. len { + data.push(try!(Serializable::read_from(buf))); + } + Result::Ok(LenPrefixed{len: len_data, data: data}) + } + + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + let len_data : L = self.data.len().into(); + try!(len_data.write_to(buf)); + let ref data = self.data; + for val in data { + try!(val.write_to(buf)); + } + Result::Ok(()) + } +} + +impl Default for LenPrefixed { + fn default() -> Self { + LenPrefixed { + len: default::Default::default(), + data: default::Default::default() + } + } +} + /// VarInt have a variable size (between 1 and 5 bytes) when encoded based /// on the size of the number +#[derive(Clone, Copy)] pub struct VarInt(i32); +impl Lengthable for VarInt {} + impl Serializable for VarInt { /// Decodes a VarInt from the Reader fn read_from(buf: &mut io::Read) -> Result { @@ -204,8 +362,21 @@ impl default::Default for VarInt { fn default() -> VarInt { VarInt(0) } } +impl convert::Into for VarInt { + fn into(self) -> usize { + self.0 as usize + } +} + +impl convert::From for VarInt { + fn from(u: usize) -> VarInt { + VarInt(u as i32) + } +} + /// Direction is used to define whether packets are going to the /// server or the client. +#[derive(Clone, Copy)] pub enum Direction { Serverbound, Clientbound @@ -252,16 +423,18 @@ impl ::std::fmt::Display for Error { } } - -/// Helper for empty structs -pub struct Empty; - pub struct Conn { stream: TcpStream, host: String, port: u16, direction: Direction, state: State, + + cipher: Option, + + compression_threshold: i32, + compression_read: Option>>>, // Pending reset support + compression_write: Option>>>, } impl Conn { @@ -278,27 +451,58 @@ impl Conn { port: parts[1].parse().unwrap(), direction: Direction::Serverbound, state: State::Handshaking, + cipher: Option::None, + compression_threshold: -1, + compression_read: Option::None, + compression_write: Option::None, }) } - // TODO: compression and encryption - pub fn write_packet(&mut self, packet: T) -> Result<(), Error> { let mut buf = Vec::new(); try!(VarInt(packet.packet_id()).write_to(&mut buf)); try!(packet.write(&mut buf)); - try!(VarInt(buf.len() as i32).write_to(&mut self.stream)); - try!(self.stream.write_all(&buf.into_boxed_slice())); + + let mut extra = if self.compression_threshold >= 0 { 1 } else { 0 }; + if self.compression_threshold >= 0 && buf.len() as i32 > self.compression_threshold { + extra = 0; + let uncompressed_size = buf.len(); + let mut new = Vec::new(); + try!(VarInt(uncompressed_size as i32).write_to(&mut new)); + let mut write = self.compression_write.as_mut().unwrap(); + write.reset(io::Cursor::new(buf)); + try!(write.read_to_end(&mut new)); + buf = new; + } + + try!(VarInt(buf.len() as i32 + extra).write_to(self)); + if self.compression_threshold >= 0 && extra == 1 { + try!(VarInt(0).write_to(self)); + } + try!(self.write_all(&buf.into_boxed_slice())); Result::Ok(()) } pub fn read_packet(&mut self) -> Result { - let len = try!(VarInt::read_from(&mut self.stream)).0 as usize; + let len = try!(VarInt::read_from(self)).0 as usize; let mut ibuf = Vec::with_capacity(len); - try!((&mut self.stream).take(len as u64).read_to_end(&mut ibuf)); + try!(self.take(len as u64).read_to_end(&mut ibuf)); let mut buf = io::Cursor::new(ibuf); + + if self.compression_threshold >= 0 { + let uncompressed_size = try!(VarInt::read_from(&mut buf)).0; + if uncompressed_size != 0 { + let mut new = Vec::with_capacity(uncompressed_size as usize); + { + let mut reader = self.compression_read.as_mut().unwrap(); + reader.reset(buf); + try!(reader.read_to_end(&mut new)); + } + buf = io::Cursor::new(new); + } + } let id = try!(VarInt::read_from(&mut buf)).0; let dir = match self.direction { @@ -317,7 +521,70 @@ impl Conn { } Result::Ok(val) }, - None => Result::Err(Error::Err("missing packet".to_string())) + // FIXME + None => Result::Ok(packet::Packet::StatusRequest(packet::status::serverbound::StatusRequest{empty:()}))//Result::Err(Error::Err("missing packet".to_string())) + } + } + + pub fn enable_encyption(&mut self, key: &Vec, decrypt: bool) { + self.cipher = Option::Some(openssl::EVPCipher::new(key, key, decrypt)); + } + + pub fn set_compresssion(&mut self, threshold: i32, read: bool) { + self.compression_threshold = threshold; + if !read { + self.compression_write = Some(ZlibEncoder::new(io::Cursor::new(Vec::new()), flate2::Compression::Default)); + } else { + self.compression_read = Some(ZlibDecoder::new(io::Cursor::new(Vec::new()))); + } + } +} + +impl Read for Conn { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self.cipher.as_mut() { + Option::None => self.stream.read(buf), + Option::Some(cipher) => { + let ret = try!(self.stream.read(buf)); + let data = cipher.decrypt(&buf[..ret]); + for i in 0 .. ret { + buf[i] = data[i]; + } + Ok(ret) + }, + } + } +} + +impl Write for Conn { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self.cipher.as_mut() { + Option::None => self.stream.write(buf), + Option::Some(cipher) => { + let data = cipher.encrypt(buf); + try!(self.stream.write_all(&data[..])); + Ok(buf.len()) + }, + } + } + + fn flush(&mut self) -> io::Result<()> { + self.stream.flush() + } +} + +impl Clone for Conn { + fn clone(&self) -> Self { + Conn { + stream: self.stream.try_clone().unwrap(), + host: self.host.clone(), + port: self.port, + direction: self.direction, + state: self.state, + cipher: Option::None, + compression_threshold: self.compression_threshold, + compression_read: Option::None, + compression_write: Option::None, } } } @@ -336,22 +603,77 @@ fn test() { protocol_version: VarInt(69), host: "localhost".to_string(), port: 25565, - next: VarInt(1), + next: VarInt(2), }).unwrap(); - c.state = State::Status; - c.write_packet(packet::status::serverbound::StatusRequest{empty: Empty}).unwrap(); + c.state = State::Login; + c.write_packet(packet::login::serverbound::LoginStart{username: "Think".to_string()}).unwrap(); - match c.read_packet().unwrap() { - packet::Packet::StatusResponse(val) => println!("{}", val.status), + let packet = match c.read_packet().unwrap() { + packet::Packet::EncryptionRequest(val) => val, _ => panic!("Wrong packet"), - } + }; - c.write_packet(packet::status::serverbound::StatusPing{ping: 4433}).unwrap(); + let mut key = openssl::PublicKey::new(&packet.public_key.data); + let shared = openssl::gen_random(16); - match c.read_packet().unwrap() { - packet::Packet::StatusPong(val) => println!("{}", val.ping), - _ => panic!("Wrong packet"), - } + let shared_e = key.encrypt(&shared); + let token_e = key.encrypt(&packet.verify_token.data); - panic!("TODO!"); + let profile = mojang::Profile{ + username: "Think".to_string(), + id: "b1184d43168441cfa2128b9a3df3b6ab".to_string(), + access_token: "".to_string() + }; + + profile.join_server(&packet.server_id, &shared, &packet.public_key.data); + + c.write_packet(packet::login::serverbound::EncryptionResponse{ + shared_secret: LenPrefixed::new(shared_e), + verify_token: LenPrefixed::new(token_e), + }); + + let mut read = c.clone(); + let mut write = c.clone(); + + read.enable_encyption(&shared, true); + write.enable_encyption(&shared, false); + + loop { match read.read_packet().unwrap() { + packet::Packet::LoginDisconnect(val) => { + panic!("Discconect {}", val.reason); + }, + packet::Packet::SetInitialCompression(val) => { + read.set_compresssion(val.threshold.0, true); + write.set_compresssion(val.threshold.0, false); + println!("Compression: {}", val.threshold.0) + }, + packet::Packet::LoginSuccess(val) => { + println!("Login: {} {}", val.username, val.uuid); + read.state = State::Play; + write.state = State::Play; + break; + } + _ => panic!("Unknown packet"), + } } + + let mut first = true; + let mut count = 0; + loop { match read.read_packet().unwrap() { + packet::Packet::ServerMessage(val) => println!("MSG: {}", val.message), + _ => { + if first { + println!("got packet"); + write.write_packet(packet::play::serverbound::ChatMessage{ + message: "Hello world".to_string(), + }); + first = false; + } + count += 1; + if count > 200 { + break; + } + } + } } + + unimplemented!(); } diff --git a/src/protocol/mojang.rs b/src/protocol/mojang.rs new file mode 100644 index 0000000..2941ad8 --- /dev/null +++ b/src/protocol/mojang.rs @@ -0,0 +1,65 @@ +extern crate steven_openssl as openssl; +extern crate serde_json; +extern crate hyper; + +pub struct Profile { + pub username: String, + pub id: String, + pub access_token: String +} + +const JOIN_URL: &'static str = "https://sessionserver.mojang.com/session/minecraft/join"; + +impl Profile { + pub fn join_server(&self, server_id: &String, shared_key: &Vec, public_key: &Vec) { + let mut sha1 = openssl::SHA1::new(); + sha1.update(server_id.as_bytes()); + sha1.update(&shared_key[..]); + sha1.update(&public_key[..]); + let mut hash = sha1.bytes(); + + // Mojang uses a hex method which allows for + // negatives so we have to account for that. + let negative = hash[0] & 0x80 == 0x80; + if negative { + twos_compliment(&mut hash); + } + let hash_str = hash.iter().map(|b| format!("{:02X}", b)).collect::>().connect(""); + let hash_val = hash_str.trim_matches('0'); + let hash_str = if negative { + "-".to_owned() + &hash_val[..] + } else { + hash_val.to_owned() + }; + + let join_msg = serde_json::builder::ObjectBuilder::new() + .insert("accessToken", &self.access_token) + .insert("selectedProfile", &self.id) + .insert("serverId", hash_str) + .unwrap(); + let join = serde_json::to_string(&join_msg).unwrap(); + + let client = hyper::Client::new(); + let res = client.post(JOIN_URL) + .body(&join) + .header(hyper::header::ContentType("application/json".parse().unwrap())) + .send().unwrap(); + + let ret: serde_json::Value = match serde_json::from_reader(res) { + Result::Ok(val) => val, + Result::Err(_) => return, + }; + panic!("{:?}", ret); + } +} + +fn twos_compliment(data: &mut Vec) { + let mut carry = true; + for i in (0 .. data.len()).rev() { + data[i] = !data[i]; + if carry { + carry = data[i] == 0xFF; + data[i] += 1; + } + } +} diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index 4819c8a..02d9e68 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -17,15 +17,15 @@ state_packets!( // than the hostname due to the protocol not providing // any system for custom information to be transfered // by the client to the server until after login. - Handshake => 0 { + Handshake => 0x00 { // The protocol version of the connecting client - protocol_version: VarInt, + protocol_version: VarInt =, // The hostname the client connected to - host: String, + host: String =, // The port the client connected to - port: u16, + port: u16 =, // The next protocol state the client wants - next: VarInt + next: VarInt =, } } clientbound Clientbound { @@ -33,14 +33,113 @@ state_packets!( } play Play { serverbound Serverbound { + // TabComplete is sent by the client when the client presses tab in + // the chat box. + TabComplete => 0x00 { + text: String =, + has_target: bool =, + target: Option = when(|p: &TabComplete| p.has_target == true), + } + // ChatMessage is sent by the client when it sends a chat message or + // executes a command (prefixed by '/'). + ChatMessage => 0x01 { + message: String =, + } + // ClientStatus is sent to update the client's status + ClientStatus => 0x02 { + action_id: VarInt =, + } + // ClientSettings is sent by the client to update its current settings. + ClientSettings => 0x03 { + locale: String =, + view_distance: u8 =, + chat_mode: u8 =, + chat_colors: bool =, + displayed_skin_parts: u8 =, + main_hand: VarInt =, + } + // ConfirmTransactionServerbound is a reply to ConfirmTransaction. + ConfirmTransactionServerbound => 0x04 { + id: u8 =, + action_number: i16 =, + accepted: bool =, + } + // EnchantItem is sent when the client enchants an item. + EnchantItem => 0x05 { + id: u8 =, + enchantment: u8 =, + } + // ClickWindow is sent when the client clicks in a window. + ClickWindow => 0x06 { + id: u8 =, + slot: i16 =, + button: u8 =, + action_number: u16 =, + mode: u8 =, + clicked_item: ()=, // TODO + } } clientbound Clientbound { + ServerMessage => 15 { + message: format::Component =, + position: u8 =, + } } } login Login { serverbound Serverbound { + // LoginStart is sent immeditately after switching into the login + // state. The passed username is used by the server to authenticate + // the player in online mode. + LoginStart => 0 { + username: String =, + } + // EncryptionResponse is sent as a reply to EncryptionRequest. All + // packets following this one must be encrypted with AES/CFB8 + // encryption. + EncryptionResponse => 1 { + // The key for the AES/CFB8 cipher encrypted with the + // public key + shared_secret: LenPrefixed =, + // The verify token from the request encrypted with the + // public key + verify_token: LenPrefixed =, + } } clientbound Clientbound { + // LoginDisconnect is sent by the server if there was any issues + // authenticating the player during login or the general server + // issues (e.g. too many players). + LoginDisconnect => 0 { + reason: format::Component =, + } + // EncryptionRequest is sent by the server if the server is in + // online mode. If it is not sent then its assumed the server is + // in offline mode. + EncryptionRequest => 1 { + // Generally empty, left in from legacy auth + // but is still used by the client if provided + server_id: String =, + // A RSA Public key serialized in x.509 PRIX format + public_key: LenPrefixed =, + // Token used by the server to verify encryption is working + // correctly + verify_token: LenPrefixed =, + } + // LoginSuccess is sent by the server if the player successfully + // authenicates with the session servers (online mode) or straight + // after LoginStart (offline mode). + LoginSuccess => 2 { + // String encoding of a uuid (with hyphens) + uuid: String =, + username: String =, + } + // SetInitialCompression sets the compression threshold during the + // login state. + SetInitialCompression => 3 { + // Threshold where a packet should be sent compressed + threshold: VarInt =, + } } } status Status { @@ -50,14 +149,14 @@ state_packets!( // to signal the server to send a StatusResponse to the // client StatusRequest => 0 { - empty: Empty - }, + empty: () =, + } // StatusPing is sent by the client after recieving a // StatusResponse. The client uses the time from sending // the ping until the time of recieving a pong to measure // the latency between the client and the server. StatusPing => 1 { - ping: i64 + ping: i64 =, } } clientbound Clientbound { @@ -83,13 +182,13 @@ state_packets!( // "favicon": "data:image/png;base64," // } StatusResponse => 0 { - status: String - }, + status: String =, + } // StatusPong is sent as a reply to a StatusPing. // The Time field should be exactly the same as the // one sent by the client. StatusPong => 1 { - ping: i64 + ping: i64 =, } } }