# HG changeset patch # User nemo # Date 1537802328 14400 # Node ID 1f15b0ee8e34af3f242e607a5354d4040e32f9b1 # Parent 54725a1d1db8e86790c8dcd000f0662ed6bae170# Parent ced1e6ecaaad38acc996c38aaaa26b9d490a1162 merge diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/Cargo.toml --- a/gameServer2/Cargo.toml Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/Cargo.toml Mon Sep 24 11:18:48 2018 -0400 @@ -1,22 +1,28 @@ -cargo-features = ["edition"] - [package] edition = "2018" name = "hedgewars-server" version = "0.0.1" authors = [ "Andrey Korotaev " ] +[features] +official-server = ["openssl"] +tls-connections = ["openssl"] +default = [] + [dependencies] rand = "0.5" mio = "0.6" slab = "0.4" netbuf = "0.4" nom = "4.0" -env_logger = "0.4" +env_logger = "0.5" log = "0.4" -proptest = "0.8" base64 = "0.9" bitflags = "1.0" serde = "1.0" -serde_yaml = "0.7" +serde_yaml = "0.8" serde_derive = "1.0" +openssl = { version = "0.10", optional = true } + +[dev-dependencies] +proptest = "0.8" \ No newline at end of file diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/main.rs --- a/gameServer2/src/main.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/main.rs Mon Sep 24 11:18:48 2018 -0400 @@ -1,28 +1,12 @@ #![allow(unused_imports)] #![deny(bare_trait_objects)] -#![feature(rust_2018_preview)] - -extern crate rand; -extern crate mio; -extern crate slab; -extern crate netbuf; -extern crate base64; -#[macro_use] -extern crate nom; -#[macro_use] -extern crate log; -extern crate env_logger; -#[macro_use] extern crate proptest; -#[macro_use] extern crate bitflags; -extern crate serde; -extern crate serde_yaml; -#[macro_use] extern crate serde_derive; //use std::io::*; //use rand::Rng; //use std::cmp::Ordering; use mio::net::*; use mio::*; +use log::*; mod utils; mod server; @@ -32,7 +16,7 @@ use std::time::Duration; fn main() { - env_logger::init().unwrap(); + env_logger::init(); info!("Hedgewars game server, protocol {}", utils::PROTOCOL_VERSION); diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/protocol/messages.rs --- a/gameServer2/src/protocol/messages.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/protocol/messages.rs Mon Sep 24 11:18:48 2018 -0400 @@ -20,7 +20,7 @@ Nick(String), Proto(u16), Password(String, String), - Checker(u32, String, String), + Checker(u16, String, String), // lobby List, Chat(String), @@ -77,6 +77,7 @@ Bye(String), Nick(String), Proto(u16), + ServerAuth(String), LobbyLeft(String, String), LobbyJoined(Vec), ChatMsg {nick: String, msg: String}, @@ -99,10 +100,14 @@ RoundFinished, ServerMessage(String), + Notice(String), Warning(String), Error(String), Connected(u32), Unreachable, + + //Deprecated messages + LegacyReady(bool, Vec) } pub fn server_chat(msg: String) -> HWServerMessage { @@ -122,8 +127,8 @@ Ammo(n, None) => ("AMMO".to_string(), vec![n.to_string()]), Ammo(n, Some(s)) => ("AMMO".to_string(), vec![n.to_string(), s.to_string()]), - Scheme(n, None) => ("SCHEME".to_string(), vec![n.to_string()]), - Scheme(n, Some(s)) => ("SCHEME".to_string(), { + Scheme(n, s) if s.is_empty() => ("SCHEME".to_string(), vec![n.to_string()]), + Scheme(n, s) => ("SCHEME".to_string(), { let mut v = vec![n.to_string()]; v.extend(s.clone().into_iter()); v @@ -151,6 +156,7 @@ }; } +#[cfg(test)] macro_rules! several { [$part: expr] => { once($part) }; [$part: expr, $($other: expr),*] => { once($part).chain(several![$($other),*]) }; @@ -161,6 +167,7 @@ * * This is the inverse of the `message` parser. */ + #[cfg(test)] pub(crate) fn to_raw_protocol(&self) -> String { use self::HWProtocolMessage::*; match self { @@ -265,6 +272,7 @@ Bye(msg) => msg!["BYE", msg], Nick(nick) => msg!["NICK", nick], Proto(proto) => msg!["PROTO", proto], + ServerAuth(hash) => msg!["SERVER_AUTH", hash], LobbyLeft(nick, msg) => msg!["LOBBY:LEFT", nick, msg], LobbyJoined(nicks) => construct_message(&["LOBBY:JOINED"], &nicks), @@ -295,8 +303,13 @@ RoundFinished => msg!["ROUND_FINISHED"], ChatMsg {nick, msg} => msg!["CHAT", nick, msg], ServerMessage(msg) => msg!["SERVER_MESSAGE", msg], + Notice(msg) => msg!["NOTICE", msg], Warning(msg) => msg!["WARNING", msg], Error(msg) => msg!["ERROR", msg], + + LegacyReady(is_ready, nicks) => + construct_message(&[if *is_ready {"READY"} else {"NOT_READY"}], &nicks), + _ => msg!["ERROR", "UNIMPLEMENTED"], } } diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/protocol/mod.rs --- a/gameServer2/src/protocol/mod.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/protocol/mod.rs Mon Sep 24 11:18:48 2018 -0400 @@ -7,6 +7,7 @@ }; pub mod messages; +#[cfg(test)] pub mod test; mod parser; diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/protocol/parser.rs --- a/gameServer2/src/protocol/parser.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/protocol/parser.rs Mon Sep 24 11:18:48 2018 -0400 @@ -14,11 +14,15 @@ ops::Range }; use super::{ - messages::{HWProtocolMessage, HWProtocolMessage::*}, - test::gen_proto_msg + messages::{HWProtocolMessage, HWProtocolMessage::*} +}; +#[cfg(test)] +use { + super::test::gen_proto_msg, + proptest::{proptest, proptest_helper} }; use crate::server::coretypes::{ - HedgehogInfo, TeamInfo, GameCfg, VoteType + HedgehogInfo, TeamInfo, GameCfg, VoteType, MAX_HEDGEHOGS_PER_TEAM }; named!(end_of_message, tag!("\n\n")); @@ -42,7 +46,7 @@ named!(hog_line<&[u8], HedgehogInfo>, do_parse!(name: str_line >> eol >> hat: str_line >> (HedgehogInfo{name: name.to_string(), hat: hat.to_string()}))); -named!(_8_hogs<&[u8], [HedgehogInfo; 8]>, +named!(_8_hogs<&[u8], [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize]>, do_parse!(h1: hog_line >> eol >> h2: hog_line >> eol >> h3: hog_line >> eol >> h4: hog_line >> eol >> h5: hog_line >> eol >> h6: hog_line >> eol >> @@ -131,7 +135,7 @@ s: a_line >> (Password(p, s))) | do_parse!(tag!("CHECKER") >> eol >> - i: u32_line >> eol >> + i: u16_line >> eol >> n: a_line >> eol >> p: a_line >> (Checker(i, n, p))) @@ -196,7 +200,7 @@ | do_parse!(tag!("SCHEME") >> eol >> name: a_line >> values: opt!(preceded!(eol, separated_list!(eol, a_line))) >> - (GameCfg::Scheme(name, values))) + (GameCfg::Scheme(name, values.unwrap_or(Vec::new())))) | do_parse!(tag!("FEATURE_SIZE") >> eol >> value: u32_line >> (GameCfg::FeatureSize(value))) @@ -242,6 +246,7 @@ named!(pub extract_messages<&[u8], Vec >, many0!(complete!(message))); +#[cfg(test)] proptest! { #[test] fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) { diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/protocol/test.rs --- a/gameServer2/src/protocol/test.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/protocol/test.rs Mon Sep 24 11:18:48 2018 -0400 @@ -1,7 +1,7 @@ use proptest::{ test_runner::{TestRunner, Reason}, arbitrary::{any, any_with, Arbitrary, StrategyFor}, - strategy::{Strategy, BoxedStrategy, Just, Map}, + strategy::{Strategy, BoxedStrategy, Just, Map} }; use crate::server::coretypes::{GameCfg, TeamInfo, HedgehogInfo}; @@ -22,9 +22,6 @@ impl Into2> for Option{ fn into2(self) -> Option { self.map(|x| {x.0}) } } -impl Into2>> for Option>{ - fn into2(self) -> Option> { self.map(|x| {x.into2()}) } -} macro_rules! proto_msg_case { ($val: ident()) => @@ -74,7 +71,7 @@ 4 => Seed(Ascii), 5 => Template(u32), 6 => Ammo(Ascii, Option), - 7 => Scheme(Ascii, Option>), + 7 => Scheme(Ascii, Vec), 8 => Script(Ascii), 9 => Theme(Ascii), 10 => DrawnMap(Ascii)) @@ -120,7 +117,7 @@ 9 => Nick(Ascii), 10 => Proto(u16), 11 => Password(Ascii, Ascii), - 12 => Checker(u32, Ascii, Ascii), + 12 => Checker(u16, Ascii, Ascii), 13 => List(), 14 => Chat(Ascii), 15 => CreateRoom(Ascii, Option), diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/actions.rs --- a/gameServer2/src/server/actions.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/actions.rs Mon Sep 24 11:18:48 2018 -0400 @@ -143,12 +143,25 @@ }, ReactProtocolMessage(msg) => handlers::handle(server, client_id, msg), - CheckRegistered => - if server.clients[client_id].protocol_number > 0 && server.clients[client_id].nick != "" { - server.react(client_id, vec![ - JoinLobby, - ]); - }, + CheckRegistered => { + let client = &server.clients[client_id]; + if client.protocol_number > 0 && client.nick != "" { + let has_nick_clash = server.clients.iter().any( + |(id, c)| id != client_id && c.nick == client.nick); + + let actions = if !client.is_checker() && has_nick_clash { + if client.protocol_number < 38 { + vec![ByeClient("Nickname is already in use".to_string())] + } else { + server.clients[client_id].nick.clear(); + vec![Notice("NickAlreadyInUse".to_string()).send_self().action()] + } + } else { + vec![JoinLobby] + }; + server.react(client_id, actions); + } + }, JoinLobby => { server.clients[client_id].room_id = Some(server.lobby_id); @@ -466,6 +479,9 @@ None => {} } r.master_id = new_id; + if !r.is_fixed() && c.protocol_number < 42 { + r.name.replace_range(.., new_nick.as_ref().map_or("[]", String::as_str)); + } r.set_join_restriction(false); r.set_team_add_restriction(false); let is_fixed = r.is_fixed(); @@ -517,6 +533,8 @@ if !room.has_multiple_clans() { vec![Warn("The game can't be started with less than two clans!".to_string())] + } else if room.protocol_number <= 43 && room.players_number != room.ready_players_number { + vec![Warn("Not all players are ready".to_string())] } else if room.game_info.is_some() { vec![Warn("The game is already in progress".to_string())] } else { @@ -561,17 +579,13 @@ } FinishRoomGame(room_id) => { let mut actions = Vec::new(); - let old_info; - { - let r = &mut server.rooms[room_id]; - old_info = replace(&mut r.game_info, None); - r.game_info = None; - r.ready_players_number = 1; - actions.push(SendRoomUpdate(None)); - actions.push(RoundFinished.send_all().in_room(r.id).action()); - } - if let Some(info) = old_info { + let r = &mut server.rooms[room_id]; + r.ready_players_number = 1; + actions.push(SendRoomUpdate(None)); + actions.push(RoundFinished.send_all().in_room(r.id).action()); + + if let Some(info) = replace(&mut r.game_info, None) { for (_, c) in server.clients.iter() { if c.room_id == Some(room_id) && c.is_joined_mid_game() { actions.push(SendRoomData{ @@ -596,9 +610,14 @@ } else { None }).collect(); + if !nicks.is_empty() { - actions.push(ClientFlags("-r".to_string(), nicks) - .send_all().in_room(room_id).action()); + let msg = if r.protocol_number < 38 { + LegacyReady(false, nicks) + } else { + ClientFlags("-r".to_string(), nicks) + }; + actions.push(msg.send_all().in_room(room_id).action()); } server.react(client_id, actions); } diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/client.rs --- a/gameServer2/src/server/client.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/client.rs Mon Sep 24 11:18:48 2018 -0400 @@ -1,4 +1,5 @@ use super::coretypes::ClientId; +use bitflags::*; bitflags!{ pub struct ClientFlags: u8 { @@ -7,6 +8,7 @@ const IS_READY = 0b0000_0100; const IS_IN_GAME = 0b0000_1000; const IS_JOINED_MID_GAME = 0b0001_0000; + const IS_CHECKER = 0b0010_0000; const NONE = 0b0000_0000; const DEFAULT = Self::NONE.bits; @@ -17,6 +19,8 @@ pub id: ClientId, pub room_id: Option, pub nick: String, + pub web_password: String, + pub server_salt: String, pub protocol_number: u16, pub flags: ClientFlags, pub teams_in_game: u8, @@ -25,11 +29,13 @@ } impl HWClient { - pub fn new(id: ClientId) -> HWClient { + pub fn new(id: ClientId, salt: String) -> HWClient { HWClient { id, room_id: None, nick: String::new(), + web_password: String::new(), + server_salt: salt, protocol_number: 0, flags: ClientFlags::DEFAULT, teams_in_game: 0, @@ -51,10 +57,12 @@ pub fn is_ready(&self)-> bool { self.contains(ClientFlags::IS_READY) } pub fn is_in_game(&self)-> bool { self.contains(ClientFlags::IS_IN_GAME) } pub fn is_joined_mid_game(&self)-> bool { self.contains(ClientFlags::IS_JOINED_MID_GAME) } + pub fn is_checker(&self)-> bool { self.contains(ClientFlags::IS_CHECKER) } pub fn set_is_admin(&mut self, value: bool) { self.set(ClientFlags::IS_ADMIN, value) } pub fn set_is_master(&mut self, value: bool) { self.set(ClientFlags::IS_MASTER, value) } pub fn set_is_ready(&mut self, value: bool) { self.set(ClientFlags::IS_READY, value) } pub fn set_is_in_game(&mut self, value: bool) { self.set(ClientFlags::IS_IN_GAME, value) } pub fn set_is_joined_mid_game(&mut self, value: bool) { self.set(ClientFlags::IS_JOINED_MID_GAME, value) } + pub fn set_is_checker(&mut self, value: bool) { self.set(ClientFlags::IS_CHECKER, value) } } \ No newline at end of file diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/coretypes.rs --- a/gameServer2/src/server/coretypes.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/coretypes.rs Mon Sep 24 11:18:48 2018 -0400 @@ -1,6 +1,8 @@ pub type ClientId = usize; pub type RoomId = usize; +pub const MAX_HEDGEHOGS_PER_TEAM: u8 = 8; + #[derive(PartialEq, Eq, Clone, Debug)] pub enum ServerVar { MOTDNew(String), @@ -18,7 +20,7 @@ Template(u32), Ammo(String, Option), - Scheme(String, Option>), + Scheme(String, Vec), Script(String), Theme(String), DrawnMap(String) @@ -34,7 +36,7 @@ pub flag: String, pub difficulty: u8, pub hedgehogs_number: u8, - pub hedgehogs: [HedgehogInfo; 8], + pub hedgehogs: [HedgehogInfo; MAX_HEDGEHOGS_PER_TEAM as usize], } #[derive(PartialEq, Eq, Clone, Debug)] diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/handlers/checker.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gameServer2/src/server/handlers/checker.rs Mon Sep 24 11:18:48 2018 -0400 @@ -0,0 +1,18 @@ +use mio; +use log::*; + +use crate::{ + server::{ + server::HWServer, + coretypes::ClientId, + }, + protocol::messages::{ + HWProtocolMessage + }, +}; + +pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) { + match message { + _ => warn!("Unknown command"), + } +} diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/handlers/common.rs --- a/gameServer2/src/server/handlers/common.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/handlers/common.rs Mon Sep 24 11:18:48 2018 -0400 @@ -23,8 +23,8 @@ #[cfg(test)] mod tests { use super::*; - use protocol::messages::HWServerMessage::ChatMsg; - use server::actions::{ + use crate::protocol::messages::HWServerMessage::ChatMsg; + use crate::server::actions::{ Action::{self, Send}, PendingMessage, }; diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/handlers/inroom.rs --- a/gameServer2/src/server/handlers/inroom.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/handlers/inroom.rs Mon Sep 24 11:18:48 2018 -0400 @@ -2,7 +2,10 @@ use crate::{ server::{ - coretypes::{ClientId, RoomId, Voting, VoteType}, + coretypes::{ + ClientId, RoomId, Voting, VoteType, GameCfg, + MAX_HEDGEHOGS_PER_TEAM + }, server::HWServer, room::{HWRoom, RoomFlags}, actions::{Action, Action::*} @@ -20,6 +23,7 @@ }; use base64::{encode, decode}; use super::common::rnd_reply; +use log::*; #[derive(Clone)] struct ByMsg<'a> { @@ -53,7 +57,8 @@ match msg { [size, typ, body..] => VALID_MESSAGES.contains(typ) && match body { - [1...8, team, ..] if *typ == b'h' => team_indices.contains(team), + [1...MAX_HEDGEHOGS_PER_TEAM, team, ..] if *typ == b'h' => + team_indices.contains(team), _ => *typ != b'h' }, _ => false @@ -164,13 +169,20 @@ r.ready_players_number += 1; "+r" }; - c.set_is_ready(!c.is_ready()); - let mut v = - vec![ClientFlags(flags.to_string(), vec![c.nick.clone()]) - .send_all().in_room(r.id).action()]; + + let msg = if c.protocol_number < 38 { + LegacyReady(c.is_ready(), vec![c.nick.clone()]) + } else { + ClientFlags(flags.to_string(), vec![c.nick.clone()]) + }; + + let mut v = vec![msg.send_all().in_room(r.id).action()]; + if r.is_fixed() && r.ready_players_number == r.players_number { v.push(StartRoomGame(r.id)) } + + c.set_is_ready(!c.is_ready()); server.react(client_id, v); } } @@ -188,7 +200,7 @@ } else if r.is_team_add_restricted() { actions.push(Warn("This room currently does not allow adding new teams.".to_string())); } else { - let team = r.add_team(c.id, *info); + let team = r.add_team(c.id, *info, c.protocol_number < 42); c.teams_in_game += 1; c.clan = Some(team.color); actions.push(TeamAccepted(team.name.clone()) @@ -227,7 +239,7 @@ let actions = if let Some((_, team)) = r.find_team_and_owner_mut(|t| t.name == team_name) { if !c.is_master() { vec![ProtocolError("You're not the room master!".to_string())] - } else if number < 1 || number > 8 + } else if number < 1 || number > MAX_HEDGEHOGS_PER_TEAM || number > addable_hedgehogs + team.hedgehogs_number { vec![HedgehogsNumber(team.name.clone(), team.hedgehogs_number) .send_self().action()] @@ -274,6 +286,18 @@ } else if !c.is_master() { vec![ProtocolError("You're not the room master!".to_string())] } else { + let cfg = match cfg { + GameCfg::Scheme(name, mut values) => { + if c.protocol_number == 49 && values.len() >= 2 { + let mut s = "X".repeat(50); + s.push_str(&values.pop().unwrap()); + values.push(s); + } + GameCfg::Scheme(name, values) + } + cfg => cfg + }; + let v = vec![cfg.to_server_msg() .send_all().in_room(r.id).but_self().action()]; r.set_config(cfg); @@ -377,7 +401,7 @@ }, VoteType::HedgehogsPerTeam(number) => { match number { - 1...8 => None, + 1...MAX_HEDGEHOGS_PER_TEAM => None, _ => Some("/callvote hedgehogs: Specify number from 1 to 8.".to_string()) } }, diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/handlers/lobby.rs --- a/gameServer2/src/server/handlers/lobby.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/handlers/lobby.rs Mon Sep 24 11:18:48 2018 -0400 @@ -13,6 +13,7 @@ utils::is_name_illegal }; use super::common::rnd_reply; +use log::*; pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) { use crate::protocol::messages::HWProtocolMessage::*; diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/handlers/loggingin.rs --- a/gameServer2/src/server/handlers/loggingin.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/handlers/loggingin.rs Mon Sep 24 11:18:48 2018 -0400 @@ -2,6 +2,7 @@ use crate::{ server::{ + client::HWClient, server::HWServer, coretypes::ClientId, actions::{Action, Action::*} @@ -11,6 +12,29 @@ }, utils::is_name_illegal }; +#[cfg(feature = "official-server")] +use openssl::sha::sha1; +use std::fmt::{Formatter, LowerHex}; +use log::*; + +#[derive(PartialEq)] +struct Sha1Digest([u8; 20]); + +impl LowerHex for Sha1Digest { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + for byte in &self.0 { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +#[cfg(feature = "official-server")] +fn get_hash(client: &HWClient, salt1: &str, salt2: &str) -> Sha1Digest { + let s = format!("{}{}{}{}{}", salt1, salt2, + client.web_password, client.protocol_number, "!hedgewars"); + Sha1Digest(sha1(s.as_bytes())) +} pub fn handle(server: & mut HWServer, client_id: ClientId, message: HWProtocolMessage) { match message { @@ -23,7 +47,7 @@ else if !client.nick.is_empty() { vec![ProtocolError("Nickname already provided.".to_string())] } - else if is_name_illegal(&nick) { + else if is_name_illegal(&nick) { vec![ByeClient("Illegal nickname! Nicknames must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}".to_string())] } else { @@ -33,7 +57,7 @@ }; server.react(client_id, actions); - }, + } HWProtocolMessage::Proto(proto) => { let client = &mut server.clients[client_id]; let actions = if client.protocol_number != 0 { @@ -48,7 +72,28 @@ CheckRegistered] }; server.react(client_id, actions); - }, + } + #[cfg(feature = "official-server")] + HWProtocolMessage::Password(hash, salt) => { + let c = &server.clients[client_id]; + + let client_hash = get_hash(c, &salt, &c.server_salt); + let server_hash = get_hash(c, &c.server_salt, &salt); + let actions = if client_hash == server_hash { + vec![ServerAuth(format!("{:x}", server_hash)).send_self().action(), + JoinLobby] + } else { + vec![ByeClient("Authentication failed".to_string())] + }; + server.react(client_id, actions); + } + #[cfg(feature = "official-server")] + HWProtocolMessage::Checker(protocol, nick, password) => { + let c = &mut server.clients[client_id]; + c.nick = nick; + c.web_password = password; + c.set_is_checker(true); + } _ => warn!("Incorrect command in logging-in state"), } } diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/handlers/mod.rs --- a/gameServer2/src/server/handlers/mod.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/handlers/mod.rs Mon Sep 24 11:18:48 2018 -0400 @@ -12,11 +12,13 @@ HWServerMessage::* } }; +use log::*; mod loggingin; mod lobby; mod inroom; mod common; +mod checker; pub fn handle(server: &mut HWServer, client_id: ClientId, message: HWProtocolMessage) { match message { diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/network.rs --- a/gameServer2/src/server/network.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/network.rs Mon Sep 24 11:18:48 2018 -0400 @@ -1,7 +1,7 @@ extern crate slab; use std::{ - io, io::{Error, ErrorKind, Write}, + io, io::{Error, ErrorKind, Read, Write}, net::{SocketAddr, IpAddr, Ipv4Addr}, collections::HashSet, mem::{swap, replace} @@ -13,6 +13,7 @@ }; use netbuf; use slab::Slab; +use log::*; use crate::{ utils, @@ -22,6 +23,15 @@ server::{HWServer}, coretypes::ClientId }; +#[cfg(feature = "tls-connections")] +use openssl::{ + ssl::{ + SslMethod, SslContext, Ssl, SslContextBuilder, + SslVerifyMode, SslFiletype, SslOptions, + SslStreamBuilder, HandshakeError, MidHandshakeSslStream, SslStream + }, + error::ErrorStack +}; const MAX_BYTES_PER_READ: usize = 2048; @@ -35,16 +45,43 @@ type NetworkResult = io::Result<(T, NetworkClientState)>; +#[cfg(not(feature = "tls-connections"))] +pub enum ClientSocket { + Plain(TcpStream) +} + +#[cfg(feature = "tls-connections")] +pub enum ClientSocket { + SslHandshake(Option>), + SslStream(SslStream) +} + +impl ClientSocket { + fn inner(&self) -> &TcpStream { + #[cfg(not(feature = "tls-connections"))] + match self { + ClientSocket::Plain(stream) => stream, + } + + #[cfg(feature = "tls-connections")] + match self { + ClientSocket::SslHandshake(Some(builder)) => builder.get_ref(), + ClientSocket::SslHandshake(None) => unreachable!(), + ClientSocket::SslStream(ssl_stream) => ssl_stream.get_ref() + } + } +} + pub struct NetworkClient { id: ClientId, - socket: TcpStream, + socket: ClientSocket, peer_addr: SocketAddr, decoder: ProtocolDecoder, buf_out: netbuf::Buf } impl NetworkClient { - pub fn new(id: ClientId, socket: TcpStream, peer_addr: SocketAddr) -> NetworkClient { + pub fn new(id: ClientId, socket: ClientSocket, peer_addr: SocketAddr) -> NetworkClient { NetworkClient { id, socket, peer_addr, decoder: ProtocolDecoder::new(), @@ -52,31 +89,53 @@ } } - pub fn read_messages(&mut self) -> NetworkResult> { + #[cfg(feature = "tls-connections")] + fn handshake_impl(&mut self, handshake: MidHandshakeSslStream) -> io::Result { + match handshake.handshake() { + Ok(stream) => { + self.socket = ClientSocket::SslStream(stream); + debug!("TLS handshake with {} ({}) completed", self.id, self.peer_addr); + Ok(NetworkClientState::Idle) + } + Err(HandshakeError::WouldBlock(new_handshake)) => { + self.socket = ClientSocket::SslHandshake(Some(new_handshake)); + Ok(NetworkClientState::Idle) + } + Err(HandshakeError::Failure(new_handshake)) => { + self.socket = ClientSocket::SslHandshake(Some(new_handshake)); + debug!("TLS handshake with {} ({}) failed", self.id, self.peer_addr); + Err(Error::new(ErrorKind::Other, "Connection failure")) + } + Err(HandshakeError::SetupFailure(_)) => unreachable!() + } + } + + fn read_impl(decoder: &mut ProtocolDecoder, source: &mut R, + id: ClientId, addr: &SocketAddr) -> NetworkResult> { let mut bytes_read = 0; let result = loop { - match self.decoder.read_from(&mut self.socket) { + match decoder.read_from(source) { Ok(bytes) => { - debug!("Client {}: read {} bytes", self.id, bytes); + debug!("Client {}: read {} bytes", id, bytes); bytes_read += bytes; if bytes == 0 { let result = if bytes_read == 0 { - info!("EOF for client {} ({})", self.id, self.peer_addr); + info!("EOF for client {} ({})", id, addr); (Vec::new(), NetworkClientState::Closed) } else { - (self.decoder.extract_messages(), NetworkClientState::NeedsRead) + (decoder.extract_messages(), NetworkClientState::NeedsRead) }; break Ok(result); } else if bytes_read >= MAX_BYTES_PER_READ { - break Ok((self.decoder.extract_messages(), NetworkClientState::NeedsRead)) + break Ok((decoder.extract_messages(), NetworkClientState::NeedsRead)) } } Err(ref error) if error.kind() == ErrorKind::WouldBlock => { let messages = if bytes_read == 0 { Vec::new() } else { - self.decoder.extract_messages() + decoder.extract_messages() }; break Ok((messages, NetworkClientState::Idle)); } @@ -84,14 +143,32 @@ break Err(error) } }; - self.decoder.sweep(); + decoder.sweep(); result } - pub fn flush(&mut self) -> NetworkResult<()> { + pub fn read(&mut self) -> NetworkResult> { + #[cfg(not(feature = "tls-connections"))] + match self.socket { + ClientSocket::Plain(ref mut stream) => + NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr), + } + + #[cfg(feature = "tls-connections")] + match self.socket { + ClientSocket::SslHandshake(ref mut handshake_opt) => { + let handshake = std::mem::replace(handshake_opt, None).unwrap(); + Ok((Vec::new(), self.handshake_impl(handshake)?)) + }, + ClientSocket::SslStream(ref mut stream) => + NetworkClient::read_impl(&mut self.decoder, stream, self.id, &self.peer_addr) + } + } + + fn write_impl(buf_out: &mut netbuf::Buf, destination: &mut W) -> NetworkResult<()> { let result = loop { - match self.buf_out.write_to(&mut self.socket) { - Ok(bytes) if self.buf_out.is_empty() || bytes == 0 => + match buf_out.write_to(destination) { + Ok(bytes) if buf_out.is_empty() || bytes == 0 => break Ok(((), NetworkClientState::Idle)), Ok(_) => (), Err(ref error) if error.kind() == ErrorKind::Interrupted @@ -102,7 +179,30 @@ break Err(error) } }; - self.socket.flush()?; + result + } + + pub fn write(&mut self) -> NetworkResult<()> { + let result = { + #[cfg(not(feature = "tls-connections"))] + match self.socket { + ClientSocket::Plain(ref mut stream) => + NetworkClient::write_impl(&mut self.buf_out, stream) + } + + #[cfg(feature = "tls-connections")] { + match self.socket { + ClientSocket::SslHandshake(ref mut handshake_opt) => { + let handshake = std::mem::replace(handshake_opt, None).unwrap(); + Ok(((), self.handshake_impl(handshake)?)) + } + ClientSocket::SslStream(ref mut stream) => + NetworkClient::write_impl(&mut self.buf_out, stream) + } + } + }; + + self.socket.inner().flush()?; result } @@ -119,12 +219,19 @@ } } +#[cfg(feature = "tls-connections")] +struct ServerSsl { + context: SslContext +} + pub struct NetworkLayer { listener: TcpListener, server: HWServer, clients: Slab, pending: HashSet<(ClientId, NetworkClientState)>, - pending_cache: Vec<(ClientId, NetworkClientState)> + pending_cache: Vec<(ClientId, NetworkClientState)>, + #[cfg(feature = "tls-connections")] + ssl: ServerSsl } impl NetworkLayer { @@ -133,7 +240,24 @@ let clients = Slab::with_capacity(clients_limit); let pending = HashSet::with_capacity(2 * clients_limit); let pending_cache = Vec::with_capacity(2 * clients_limit); - NetworkLayer {listener, server, clients, pending, pending_cache} + + NetworkLayer { + listener, server, clients, pending, pending_cache, + #[cfg(feature = "tls-connections")] + ssl: NetworkLayer::create_ssl_context() + } + } + + #[cfg(feature = "tls-connections")] + fn create_ssl_context() -> ServerSsl { + let mut builder = SslContextBuilder::new(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + builder.set_read_ahead(true); + builder.set_certificate_file("ssl/cert.pem", SslFiletype::PEM).unwrap(); + builder.set_private_key_file("ssl/key.pem", SslFiletype::PEM).unwrap(); + builder.set_options(SslOptions::NO_COMPRESSION); + builder.set_cipher_list("DEFAULT:!LOW:!RC4:!EXP").unwrap(); + ServerSsl { context: builder.build() } } pub fn register_server(&self, poll: &Poll) -> io::Result<()> { @@ -144,7 +268,7 @@ fn deregister_client(&mut self, poll: &Poll, id: ClientId) { let mut client_exists = false; if let Some(ref client) = self.clients.get(id) { - poll.deregister(&client.socket) + poll.deregister(client.socket.inner()) .expect("could not deregister socket"); info!("client {} ({}) removed", client.id, client.peer_addr); client_exists = true; @@ -154,8 +278,8 @@ } } - fn register_client(&mut self, poll: &Poll, id: ClientId, client_socket: TcpStream, addr: SocketAddr) { - poll.register(&client_socket, Token(id), + fn register_client(&mut self, poll: &Poll, id: ClientId, client_socket: ClientSocket, addr: SocketAddr) { + poll.register(client_socket.inner(), Token(id), Ready::readable() | Ready::writable(), PollOpt::edge()) .expect("could not register socket with event loop"); @@ -180,12 +304,34 @@ } } + fn create_client_socket(&self, socket: TcpStream) -> io::Result { + #[cfg(not(feature = "tls-connections"))] { + Ok(ClientSocket::Plain(socket)) + } + + #[cfg(feature = "tls-connections")] { + let ssl = Ssl::new(&self.ssl.context).unwrap(); + let mut builder = SslStreamBuilder::new(ssl, socket); + builder.set_accept_state(); + match builder.handshake() { + Ok(stream) => + Ok(ClientSocket::SslStream(stream)), + Err(HandshakeError::WouldBlock(stream)) => + Ok(ClientSocket::SslHandshake(Some(stream))), + Err(e) => { + debug!("OpenSSL handshake failed: {}", e); + Err(Error::new(ErrorKind::Other, "Connection failure")) + } + } + } + } + pub fn accept_client(&mut self, poll: &Poll) -> io::Result<()> { let (client_socket, addr) = self.listener.accept()?; info!("Connected: {}", addr); let client_id = self.server.add_client(); - self.register_client(poll, client_id, client_socket, addr); + self.register_client(poll, client_id, self.create_client_socket(client_socket)?, addr); self.flush_server_messages(); Ok(()) @@ -205,7 +351,7 @@ client_id: ClientId) -> io::Result<()> { let messages = if let Some(ref mut client) = self.clients.get_mut(client_id) { - client.read_messages() + client.read() } else { warn!("invalid readable client: {}", client_id); Ok((Vec::new(), NetworkClientState::Idle)) @@ -246,7 +392,7 @@ client_id: ClientId) -> io::Result<()> { let result = if let Some(ref mut client) = self.clients.get_mut(client_id) { - client.flush() + client.write() } else { warn!("invalid writable client: {}", client_id); Ok(((), NetworkClientState::Idle)) diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/room.rs --- a/gameServer2/src/server/room.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/room.rs Mon Sep 24 11:18:48 2018 -0400 @@ -2,14 +2,20 @@ iter, collections::HashMap }; use crate::server::{ - coretypes::{ClientId, RoomId, TeamInfo, GameCfg, GameCfg::*, Voting}, + coretypes::{ + ClientId, RoomId, TeamInfo, GameCfg, GameCfg::*, Voting, + MAX_HEDGEHOGS_PER_TEAM + }, client::{HWClient} }; +use bitflags::*; use serde::{Serialize, Deserialize}; +use serde_derive::{Serialize, Deserialize}; use serde_yaml; -const MAX_HEDGEHOGS_IN_ROOM: u8 = 64; const MAX_TEAMS_IN_ROOM: u8 = 8; +const MAX_HEDGEHOGS_IN_ROOM: u8 = + MAX_HEDGEHOGS_PER_TEAM * MAX_HEDGEHOGS_PER_TEAM; #[derive(Clone, Serialize, Deserialize)] struct Ammo { @@ -20,7 +26,7 @@ #[derive(Clone, Serialize, Deserialize)] struct Scheme { name: String, - settings: Option> + settings: Vec } #[derive(Clone, Serialize, Deserialize)] @@ -50,7 +56,7 @@ template: 0, ammo: Ammo {name: "Default".to_string(), settings: None }, - scheme: Scheme {name: "Default".to_string(), settings: None }, + scheme: Scheme {name: "Default".to_string(), settings: Vec::new() }, script: "Normal".to_string(), theme: "\u{1f994}".to_string(), drawn_map: None @@ -176,11 +182,13 @@ MAX_HEDGEHOGS_IN_ROOM - self.hedgehogs_number() } - pub fn add_team(&mut self, owner_id: ClientId, mut team: TeamInfo) -> &TeamInfo { - team.color = iter::repeat(()).enumerate() - .map(|(i, _)| i as u8).take(u8::max_value() as usize + 1) - .find(|i| self.teams.iter().all(|(_, t)| t.color != *i )) - .unwrap_or(0u8); + pub fn add_team(&mut self, owner_id: ClientId, mut team: TeamInfo, preserve_color: bool) -> &TeamInfo { + if !preserve_color { + team.color = iter::repeat(()).enumerate() + .map(|(i, _)| i as u8).take(u8::max_value() as usize + 1) + .find(|i| self.teams.iter().all(|(_, t)| t.color != *i)) + .unwrap_or(0u8) + }; team.hedgehogs_number = if self.teams.is_empty() { self.default_hedgehog_number } else { diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/server/server.rs --- a/gameServer2/src/server/server.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/server/server.rs Mon Sep 24 11:18:48 2018 -0400 @@ -6,6 +6,9 @@ actions::{Destination, PendingMessage} }; use crate::protocol::messages::*; +use rand::{RngCore, thread_rng}; +use base64::{encode}; +use log::*; type Slab = slab::Slab; @@ -37,7 +40,10 @@ { let entry = self.clients.vacant_entry(); key = entry.key(); - let client = HWClient::new(entry.key()); + let mut salt = [0u8; 18]; + thread_rng().fill_bytes(&mut salt); + + let client = HWClient::new(entry.key(), encode(&salt)); entry.insert(client); } self.send(key, &Destination::ToSelf, HWServerMessage::Connected(utils::PROTOCOL_VERSION)); diff -r ced1e6ecaaad -r 1f15b0ee8e34 gameServer2/src/utils.rs --- a/gameServer2/src/utils.rs Thu Sep 20 13:07:21 2018 +0200 +++ b/gameServer2/src/utils.rs Mon Sep 24 11:18:48 2018 -0400 @@ -20,4 +20,48 @@ tmp.push(msg.clone().count() as u8); tmp.extend(msg); encode(&tmp) +} + +pub fn protocol_version_string(protocol_number: u16) -> &'static str { + match protocol_number { + 17 => "0.9.7-dev", + 19 => "0.9.7", + 20 => "0.9.8-dev", + 21 => "0.9.8", + 22 => "0.9.9-dev", + 23 => "0.9.9", + 24 => "0.9.10-dev", + 25 => "0.9.10", + 26 => "0.9.11-dev", + 27 => "0.9.11", + 28 => "0.9.12-dev", + 29 => "0.9.12", + 30 => "0.9.13-dev", + 31 => "0.9.13", + 32 => "0.9.14-dev", + 33 => "0.9.14", + 34 => "0.9.15-dev", + 35 => "0.9.14.1", + 37 => "0.9.15", + 38 => "0.9.16-dev", + 39 => "0.9.16", + 40 => "0.9.17-dev", + 41 => "0.9.17", + 42 => "0.9.18-dev", + 43 => "0.9.18", + 44 => "0.9.19-dev", + 45 => "0.9.19", + 46 => "0.9.20-dev", + 47 => "0.9.20", + 48 => "0.9.21-dev", + 49 => "0.9.21", + 50 => "0.9.22-dev", + 51 => "0.9.22", + 52 => "0.9.23-dev", + 53 => "0.9.23", + 54 => "0.9.24-dev", + 55 => "0.9.24", + 56 => "0.9.25-dev", + _ => "Unknown" + } } \ No newline at end of file