From: Ian Jackson Date: Sat, 22 Aug 2020 21:44:52 +0000 (+0100) Subject: move several things into spec.rs X-Git-Tag: otter-0.2.0~1102 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=bf06e3d9a864ff1792b91b1f8f8d2105f4a31856;p=otter.git move several things into spec.rs --- diff --git a/src/cmdlistener.rs b/src/cmdlistener.rs index 9b3d7a80..e5e26937 100644 --- a/src/cmdlistener.rs +++ b/src/cmdlistener.rs @@ -24,121 +24,16 @@ type ME = MgmtError; from_instance_lock_error!{MgmtError} const USERLIST : &str = "/etc/userlist"; +const CREATE_PIECES_MAX : u32 = 300; -// ---------- entrypoint for the rest of the program ---------- +const XXX_START_POS : Pos = [20,20]; +const XXX_DEFAULT_POSD : Pos = [5,5]; pub struct CommandListener { listener : UnixListener, } -// ---------- core listener implementation ---------- - -struct CommandStream<'d> { - euid : Result, - desc : &'d str, - scope : Option, - chan : MgmtChannel, -} - -impl CommandStream<'_> { - #[throws(CSE)] - pub fn mainloop(mut self) { - loop { - use MgmtChannelReadError::*; - let resp = match self.chan.read() { - Ok(cmd) => match execute(&mut self, cmd) { - Ok(resp) => resp, - Err(error) => MgmtResponse::Error { error }, - }, - Err(EOF) => break, - Err(IO(e)) => Err(e).context("read command stream")?, - Err(Parse(s)) => MgmtResponse::Error { error : ParseFailed(s) }, - }; - self.chan.write(&resp).context("swrite command stream")?; - } - } - - #[throws(MgmtError)] - fn get_scope(&self) -> &ManagementScope { - self.scope.as_ref().ok_or(NoScope)? - } -} - -impl CommandListener { - #[throws(StartupError)] - pub fn new() -> Self { - let path = SOCKET_PATH; - match fs::remove_file(path) { - Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()), - r => r, - } - .with_context(|| format!("remove socket {:?} before we bind", &path))?; - let listener = UnixListener::bind(path) - .with_context(|| format!("bind command socket {:?}", &path))?; - - fs::set_permissions(path, unix::fs::PermissionsExt::from_mode(0o666)) - .with_context(|| format!("chmod sommand socket {:?}", &path))?; - - CommandListener { listener } - } - - #[throws(StartupError)] - pub fn spawn(mut self) { - thread::spawn(move ||{ - loop { - self.accept_one().unwrap_or_else( - |e| eprintln!("accept/spawn failed: {:?}", e) - ); - } - }) - } - - #[throws(CSE)] - fn accept_one(&mut self) { - let (conn, _caller) = self.listener.accept().context("accept")?; - let mut desc = format!("{:>5}", conn.as_raw_fd()); - eprintln!("command connection {}: accepted", &desc); - thread::spawn(move||{ - match (||{ - let euid = conn.initial_peer_credentials() - .map(|creds| creds.euid()) - .map_err(|e| ConnectionEuidDiscoverEerror(format!("{}", e))); - - #[derive(Error,Debug)] - struct EuidLookupError(String); - display_as_debug!{EuidLookupError} - impl From<&E> for EuidLookupError where E : Display { - fn from(e: &E) -> Self { EuidLookupError(format!("{}",e)) } - } - - let user_desc : String = (||{ - let euid = euid.clone()?; - let pwent = Passwd::from_uid(euid); - let show_username = - pwent.map_or_else(|| format!("", euid), - |p| p.name); - >::Ok(show_username) - })().unwrap_or_else(|e| format!("", e)); - write!(&mut desc, " user={}", user_desc)?; - - let chan = MgmtChannel::new(conn)?; - - let cs = CommandStream { - scope: None, desc: &desc, - chan, euid, - }; - cs.mainloop()?; - - >::Ok(()) - })() { - Ok(()) => eprintln!("command connection {}: disconnected", &desc), - Err(e) => eprintln!("command connection {}: error: {:?}", &desc, e), - } - }); - } -} - -// ---------- core management channel implementation ---------- +// ========== management API ========== // ---------- management command implementations @@ -213,148 +108,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse { } } -//---------- authorisation ---------- - -#[derive(Debug,Error,Clone)] -#[error("connection euid lookup failed (at connection initiation): {0}")] -pub struct ConnectionEuidDiscoverEerror(String); - -impl CommandStream<'_> { - #[throws(AuthorisationError)] - fn authorised_uid(&self, wanted: Option, xinfo: Option<&str>) - -> Authorised<(Passwd,uid_t),> { - let client_euid = *self.euid.as_ref().map_err(|e| e.clone())?; - let server_euid = unsafe { libc::getuid() }; - if client_euid == 0 || - client_euid == server_euid || - Some(client_euid) == wanted - { - return Authorised::authorise(); - } - throw!(anyhow!("{}: euid mismatch: client={:?} server={:?} wanted={:?}{}", - &self.desc, client_euid, server_euid, wanted, - xinfo.unwrap_or(""))); - } - - fn map_auth_err(&self, ae: AuthorisationError) -> MgmtError { - eprintln!("command connection {}: authorisation error: {}", - self.desc, ae.0); - MgmtError::AuthorisationError - } -} - -#[throws(MgmtError)] -fn authorise_scope(cs: &CommandStream, wanted: &ManagementScope) - -> AuthorisedSatisfactory { - do_authorise_scope(cs, wanted) - .map_err(|e| cs.map_auth_err(e))? -} - -#[throws(AuthorisationError)] -fn do_authorise_scope(cs: &CommandStream, wanted: &ManagementScope) - -> AuthorisedSatisfactory { - type AS = (T, ManagementScope); - - match &wanted { - - ManagementScope::Server => { - let y : AS< - Authorised<(Passwd,uid_t)>, - > = { - let ok = cs.authorised_uid(None,None)?; - (ok, - ManagementScope::Server) - }; - return y.into() - }, - - ManagementScope::Unix { user: wanted } => { - let y : AS< - Authorised<(Passwd,uid_t)>, - > = { - struct AuthorisedIf { authorised_for : Option }; - - let pwent = Passwd::from_name(&wanted) - .map_err( - |e| anyhow!("looking up requested username {:?}: {:?}", - &wanted, &e) - )? - .ok_or_else( - || AuthorisationError(format!( - "requested username {:?} not found", &wanted - )) - )?; - - let (in_userlist, xinfo) = (||{ >::Ok({ - let allowed = BufReader::new(match File::open(USERLIST) { - Err(e) if e.kind() == ErrorKind::NotFound => { - return Ok(( - AuthorisedIf{ authorised_for: None }, - Some(format!(" user list {} does not exist", USERLIST)) - )) - }, - r => r, - }?); - allowed - .lines() - .filter_map(|le| match le { - Ok(l) if l.trim() == wanted => Some( - Ok(( - AuthorisedIf{ authorised_for: Some(pwent.uid) }, - None - )) - ), - Ok(_) => None, - Err(e) => Some(>::Err(e.into())), - }) - .next() - .unwrap_or_else( - || Ok(( - AuthorisedIf{ authorised_for: None }, - Some(format!(" requested username {:?} not in {}", - &wanted, USERLIST)), - )) - )? - })})()?; - - let AuthorisedIf{ authorised_for } = in_userlist; - let info = xinfo.as_deref(); - let ok = cs.authorised_uid(authorised_for, info)?; - (ok, - ManagementScope::Unix { user: pwent.name }) - }; - y.into() - }, - - } -} - - -#[throws(ME)] -fn execute_for_game(cs: &CommandStream, ig: &mut InstanceGuard, - mut insns: Vec, - how: MgmtGameUpdateMode) -> MgmtResponse { - let mut uh = UpdateHandler::from_how(how); - let mut responses = Vec::with_capacity(insns.len()); - let ok = (||{ - for insn in insns.drain(0..) { - let (updates, resp) = execute_game_insn(cs, ig, insn)?; - uh.accumulate(ig, updates)?; - responses.push(resp); - } - uh.complete(cs,ig)?; - Ok(None) - })(); - MgmtResponse::AlterGame { - responses, - error: ok.unwrap_or_else(Some) - } -} - -const XXX_START_POS : Pos = [20,20]; -const XXX_DEFAULT_POSD : Pos = [5,5]; - -const CREATE_PIECES_MAX : u32 = 300; +// ---------- game command implementations ---------- type ExecuteGameInsnResults = ( ExecuteGameChangeUpdates, @@ -510,7 +264,30 @@ fn execute_game_insn(cs: &CommandStream, }, } } -//---------- game update processing ---------- + +// ---------- how to execute game commands & handle their updates ---------- + +#[throws(ME)] +fn execute_for_game(cs: &CommandStream, ig: &mut InstanceGuard, + mut insns: Vec, + how: MgmtGameUpdateMode) -> MgmtResponse { + let mut uh = UpdateHandler::from_how(how); + let mut responses = Vec::with_capacity(insns.len()); + let ok = (||{ + for insn in insns.drain(0..) { + let (updates, resp) = execute_game_insn(cs, ig, insn)?; + uh.accumulate(ig, updates)?; + responses.push(resp); + } + uh.complete(cs,ig)?; + Ok(None) + })(); + MgmtResponse::AlterGame { + responses, + error: ok.unwrap_or_else(Some) + } +} + #[derive(Debug,Default)] struct UpdateHandlerBulk { @@ -596,6 +373,231 @@ impl UpdateHandler { } } +// ========== general implementation ========== + +// ---------- core listener implementation ---------- + +struct CommandStream<'d> { + euid : Result, + desc : &'d str, + scope : Option, + chan : MgmtChannel, +} + +impl CommandStream<'_> { + #[throws(CSE)] + pub fn mainloop(mut self) { + loop { + use MgmtChannelReadError::*; + let resp = match self.chan.read() { + Ok(cmd) => match execute(&mut self, cmd) { + Ok(resp) => resp, + Err(error) => MgmtResponse::Error { error }, + }, + Err(EOF) => break, + Err(IO(e)) => Err(e).context("read command stream")?, + Err(Parse(s)) => MgmtResponse::Error { error : ParseFailed(s) }, + }; + self.chan.write(&resp).context("swrite command stream")?; + } + } + + #[throws(MgmtError)] + fn get_scope(&self) -> &ManagementScope { + self.scope.as_ref().ok_or(NoScope)? + } +} + +impl CommandListener { + #[throws(StartupError)] + pub fn new() -> Self { + let path = SOCKET_PATH; + match fs::remove_file(path) { + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()), + r => r, + } + .with_context(|| format!("remove socket {:?} before we bind", &path))?; + let listener = UnixListener::bind(path) + .with_context(|| format!("bind command socket {:?}", &path))?; + + fs::set_permissions(path, unix::fs::PermissionsExt::from_mode(0o666)) + .with_context(|| format!("chmod sommand socket {:?}", &path))?; + + CommandListener { listener } + } + + #[throws(StartupError)] + pub fn spawn(mut self) { + thread::spawn(move ||{ + loop { + self.accept_one().unwrap_or_else( + |e| eprintln!("accept/spawn failed: {:?}", e) + ); + } + }) + } + + #[throws(CSE)] + fn accept_one(&mut self) { + let (conn, _caller) = self.listener.accept().context("accept")?; + let mut desc = format!("{:>5}", conn.as_raw_fd()); + eprintln!("command connection {}: accepted", &desc); + thread::spawn(move||{ + match (||{ + let euid = conn.initial_peer_credentials() + .map(|creds| creds.euid()) + .map_err(|e| ConnectionEuidDiscoverEerror(format!("{}", e))); + + #[derive(Error,Debug)] + struct EuidLookupError(String); + display_as_debug!{EuidLookupError} + impl From<&E> for EuidLookupError where E : Display { + fn from(e: &E) -> Self { EuidLookupError(format!("{}",e)) } + } + + let user_desc : String = (||{ + let euid = euid.clone()?; + let pwent = Passwd::from_uid(euid); + let show_username = + pwent.map_or_else(|| format!("", euid), + |p| p.name); + >::Ok(show_username) + })().unwrap_or_else(|e| format!("", e)); + write!(&mut desc, " user={}", user_desc)?; + + let chan = MgmtChannel::new(conn)?; + + let cs = CommandStream { + scope: None, desc: &desc, + chan, euid, + }; + cs.mainloop()?; + + >::Ok(()) + })() { + Ok(()) => eprintln!("command connection {}: disconnected", &desc), + Err(e) => eprintln!("command connection {}: error: {:?}", &desc, e), + } + }); + } +} + +//---------- authorisation ---------- + +#[derive(Debug,Error,Clone)] +#[error("connection euid lookup failed (at connection initiation): {0}")] +pub struct ConnectionEuidDiscoverEerror(String); + +impl CommandStream<'_> { + #[throws(AuthorisationError)] + fn authorised_uid(&self, wanted: Option, xinfo: Option<&str>) + -> Authorised<(Passwd,uid_t),> { + let client_euid = *self.euid.as_ref().map_err(|e| e.clone())?; + let server_euid = unsafe { libc::getuid() }; + if client_euid == 0 || + client_euid == server_euid || + Some(client_euid) == wanted + { + return Authorised::authorise(); + } + throw!(anyhow!("{}: euid mismatch: client={:?} server={:?} wanted={:?}{}", + &self.desc, client_euid, server_euid, wanted, + xinfo.unwrap_or(""))); + } + + fn map_auth_err(&self, ae: AuthorisationError) -> MgmtError { + eprintln!("command connection {}: authorisation error: {}", + self.desc, ae.0); + MgmtError::AuthorisationError + } +} + +#[throws(MgmtError)] +fn authorise_scope(cs: &CommandStream, wanted: &ManagementScope) + -> AuthorisedSatisfactory { + do_authorise_scope(cs, wanted) + .map_err(|e| cs.map_auth_err(e))? +} + +#[throws(AuthorisationError)] +fn do_authorise_scope(cs: &CommandStream, wanted: &ManagementScope) + -> AuthorisedSatisfactory { + type AS = (T, ManagementScope); + + match &wanted { + + ManagementScope::Server => { + let y : AS< + Authorised<(Passwd,uid_t)>, + > = { + let ok = cs.authorised_uid(None,None)?; + (ok, + ManagementScope::Server) + }; + return y.into() + }, + + ManagementScope::Unix { user: wanted } => { + let y : AS< + Authorised<(Passwd,uid_t)>, + > = { + struct AuthorisedIf { authorised_for : Option }; + + let pwent = Passwd::from_name(&wanted) + .map_err( + |e| anyhow!("looking up requested username {:?}: {:?}", + &wanted, &e) + )? + .ok_or_else( + || AuthorisationError(format!( + "requested username {:?} not found", &wanted + )) + )?; + + let (in_userlist, xinfo) = (||{ >::Ok({ + let allowed = BufReader::new(match File::open(USERLIST) { + Err(e) if e.kind() == ErrorKind::NotFound => { + return Ok(( + AuthorisedIf{ authorised_for: None }, + Some(format!(" user list {} does not exist", USERLIST)) + )) + }, + r => r, + }?); + allowed + .lines() + .filter_map(|le| match le { + Ok(l) if l.trim() == wanted => Some( + Ok(( + AuthorisedIf{ authorised_for: Some(pwent.uid) }, + None + )) + ), + Ok(_) => None, + Err(e) => Some(>::Err(e.into())), + }) + .next() + .unwrap_or_else( + || Ok(( + AuthorisedIf{ authorised_for: None }, + Some(format!(" requested username {:?} not in {}", + &wanted, USERLIST)), + )) + )? + })})()?; + + let AuthorisedIf{ authorised_for } = in_userlist; + let info = xinfo.as_deref(); + let ok = cs.authorised_uid(authorised_for, info)?; + (ok, + ManagementScope::Unix { user: pwent.name }) + }; + y.into() + }, + + } +} + use authproofs::*; use authproofs::AuthorisationError; diff --git a/src/gamestate.rs b/src/gamestate.rs index b9ad1171..73d03025 100644 --- a/src/gamestate.rs +++ b/src/gamestate.rs @@ -9,11 +9,6 @@ slotmap::new_key_type!{ pub struct PieceId; } -define_index_type! { - #[derive(Default)] - pub struct FaceId = u8; -} - #[derive(Copy,Clone,Debug,Ord,PartialOrd,Eq,PartialEq)] #[derive(Serialize,Deserialize)] #[serde(transparent)] diff --git a/src/global.rs b/src/global.rs index 7bc960b6..8ef6eca8 100644 --- a/src/global.rs +++ b/src/global.rs @@ -9,10 +9,6 @@ use std::sync::PoisonError; visible_slotmap_key!{ ClientId('C') } -#[derive(Clone,Debug,Eq,PartialEq,Ord,PartialOrd,Hash,Serialize,Deserialize)] -#[serde(transparent)] -pub struct RawToken (pub String); - // ---------- public data structure ---------- #[derive(Debug,Serialize,Deserialize)] diff --git a/src/imports.rs b/src/imports.rs index a9e44f47..d1f04b38 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -84,6 +84,4 @@ pub type AE = anyhow::Error; pub type OE = OnlineError; pub type SvgData = Vec; -pub type Coord = isize; -pub type Pos = [Coord; 2]; pub type Colour = String; diff --git a/src/spec.rs b/src/spec.rs index 5470e155..dc59944a 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -1,7 +1,26 @@ +// game specs -#![allow(dead_code)] +use serde::{Serialize,Deserialize}; +use fehler::throws; +use index_vec::define_index_type; +use crate::gamestate::PieceSpec; +use std::fmt::Debug; +use implementation::PlayerAccessSpec; -use crate::imports::*; +//---------- common types ---------- + +pub type Coord = isize; + +pub type Pos = [Coord; 2]; + +#[derive(Clone,Debug,Eq,PartialEq,Ord,PartialOrd,Hash,Serialize,Deserialize)] +#[serde(transparent)] +pub struct RawToken (pub String); + +define_index_type! { + #[derive(Default)] + pub struct FaceId = u8; +} //---------- Table TOML file ---------- @@ -38,38 +57,44 @@ pub struct PiecesSpec { pub count : Option, pub face : Option, #[serde(flatten)] - pub info : Box, + pub info : Box, // see pieces.rs } //---------- Implementation ---------- -#[typetag::serde(tag="access")] -pub trait PlayerAccessSpec : Debug { - fn token_mgi(&self, _player: PlayerId) -> Option { - None +mod implementation { + use super::*; + use crate::imports::*; + type Insn = crate::commands::MgmtGameInstruction; + + #[typetag::serde(tag="access")] + pub trait PlayerAccessSpec : Debug { + fn token_mgi(&self, _player: PlayerId) -> Option { + None + } + fn deliver_tokens(&self, ps: &PlayerSpec, tokens: &[RawToken]) + -> Result<(),AE>; } - fn deliver_tokens(&self, ps: &PlayerSpec, tokens: &[RawToken]) - -> Result<(),AE>; -} -#[typetag::serde] -impl PlayerAccessSpec for FixedToken { - fn token_mgi(&self, player: PlayerId) -> Option { - Some(MgmtGameInstruction::SetFixedPlayerAccess { - player, - token: self.token.clone(), - }) + #[typetag::serde] + impl PlayerAccessSpec for FixedToken { + fn token_mgi(&self, player: PlayerId) -> Option { + Some(Insn::SetFixedPlayerAccess { + player, + token: self.token.clone(), + }) + } + #[throws(AE)] + fn deliver_tokens(&self, _ps: &PlayerSpec, _tokens: &[RawToken]) { } } - #[throws(AE)] - fn deliver_tokens(&self, _ps: &PlayerSpec, _tokens: &[RawToken]) { } -} -#[typetag::serde] -impl PlayerAccessSpec for TokenOnStdout { - #[throws(AE)] - fn deliver_tokens(&self, ps: &PlayerSpec, tokens: &[RawToken]) { - for token in tokens { - println!("access nick={:?} token={}", &ps.nick, token.0); + #[typetag::serde] + impl PlayerAccessSpec for TokenOnStdout { + #[throws(AE)] + fn deliver_tokens(&self, ps: &PlayerSpec, tokens: &[RawToken]) { + for token in tokens { + println!("access nick={:?} token={}", &ps.nick, token.0); + } } } }