--- 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 <a.korotaev@hedgewars.org>" ]
+[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
--- 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);
--- 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<String>),
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<String>)
}
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"],
}
}
--- 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;
--- 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<HWProtocolMessage> >, many0!(complete!(message)));
+#[cfg(test)]
proptest! {
#[test]
fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) {
--- 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<Option<String>> for Option<Ascii>{
fn into2(self) -> Option<String> { self.map(|x| {x.0}) }
}
-impl Into2<Option<Vec<String>>> for Option<Vec<Ascii>>{
- fn into2(self) -> Option<Vec<String>> { 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<Ascii>),
- 7 => Scheme(Ascii, Option<Vec<Ascii>>),
+ 7 => Scheme(Ascii, Vec<Ascii>),
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<Ascii>),
--- 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);
}
--- 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<usize>,
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
--- 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<String>),
- Scheme(String, Option<Vec<String>>),
+ Scheme(String, Vec<String>),
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)]
--- /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"),
+ }
+}
--- 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,
};
--- 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())
}
},
--- 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::*;
--- 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"),
}
}
--- 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 {
--- 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<T> = io::Result<(T, NetworkClientState)>;
+#[cfg(not(feature = "tls-connections"))]
+pub enum ClientSocket {
+ Plain(TcpStream)
+}
+
+#[cfg(feature = "tls-connections")]
+pub enum ClientSocket {
+ SslHandshake(Option<MidHandshakeSslStream<TcpStream>>),
+ SslStream(SslStream<TcpStream>)
+}
+
+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<Vec<HWProtocolMessage>> {
+ #[cfg(feature = "tls-connections")]
+ fn handshake_impl(&mut self, handshake: MidHandshakeSslStream<TcpStream>) -> io::Result<NetworkClientState> {
+ 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<R: Read>(decoder: &mut ProtocolDecoder, source: &mut R,
+ id: ClientId, addr: &SocketAddr) -> NetworkResult<Vec<HWProtocolMessage>> {
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<Vec<HWProtocolMessage>> {
+ #[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<W: Write>(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<NetworkClient>,
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<ClientSocket> {
+ #[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))
--- 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<Vec<String>>
+ settings: Vec<String>
}
#[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 {
--- 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<T> = slab::Slab<T>;
@@ -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));
--- 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