From 2fe2e8cdbc3bb520994ff0944188f2fa657c546a Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 17:49:08 +0100 Subject: v4.0.0: require HELLO MAJOR.MINOR lockstep before auth --- src/server/core.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'src/server/core.rs') diff --git a/src/server/core.rs b/src/server/core.rs index 89264f3..3cd6469 100644 --- a/src/server/core.rs +++ b/src/server/core.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use crate::blackjack::BlackjackState; use crate::crash::CrashState; use crate::server::database; +use crate::version::{download_url, hello_line, Version, VERSION}; use crate::server::games::blackjack::{BlackjackGame, BlackjackHand}; use crate::server::games::crash::{Clock, CrashGame, CrashRound, RealClock}; use crate::server::games::registry::GameRegistry; @@ -15,6 +16,7 @@ use crate::server::games::traits::{GameError, GamePhase, GameState}; #[derive(Debug, Default)] pub struct ConnectionState { user: Option, + greeted: bool, } /// Settle reservations left behind by a previous process. @@ -32,6 +34,11 @@ impl ConnectionState { pub fn user(&self) -> Option<&str> { self.user.as_deref() } + + /// True once the client has completed the version handshake. + pub fn greeted(&self) -> bool { + self.greeted + } } pub struct ServerCore { @@ -91,7 +98,14 @@ impl ServerCore { pub fn handle_message(&mut self, state: &mut ConnectionState, msg: &str) -> String { let parts: Vec<&str> = msg.splitn(5, ' ').collect(); + + // Nothing but the handshake is accepted on a fresh connection. + if parts[0] != "HELLO" && !state.greeted { + return "ERROR HELLO required".to_string(); + } + match parts[0] { + "HELLO" => self.handle_hello(state, &parts), "GAME_LIST" => { let mut games = self.registry.list_games(); games.sort(); @@ -193,6 +207,27 @@ impl ServerCore { } } + fn handle_hello(&mut self, state: &mut ConnectionState, parts: &[&str]) -> String { + if parts.len() != 2 { + return "ERROR Invalid HELLO format. Usage: HELLO ".to_string(); + } + + let (Some(client), Some(server)) = (Version::parse(parts[1]), Version::parse(VERSION)) else { + return "ERROR Invalid version".to_string(); + }; + + if !client.compatible_with(server) { + return format!( + "ERROR Version mismatch: client {}, server {VERSION}. Get {VERSION} at {}", + parts[1], + download_url() + ); + } + + state.greeted = true; + hello_line(VERSION, VERSION, &download_url()) + } + fn handle_register(&mut self, username: &str, credential: &str) -> String { if database::get_hashed_password(&self.conn, username).is_some() { return "ERROR User already exists".to_string(); -- cgit v1.2.3