merge
authornemo
Mon, 24 Sep 2018 11:18:48 -0400
changeset 13830 1f15b0ee8e34
parent 13829 54725a1d1db8 (diff)
parent 13828 ced1e6ecaaad (current diff)
child 13831 5c6052351991
merge
--- 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