diff --git a/examples/checker/checker_rust/Cargo.toml b/examples/checker/checker_rust/Cargo.toml new file mode 100644 index 0000000..5ad3019 --- /dev/null +++ b/examples/checker/checker_rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "examplechecker" +version = "0.1.0" +authors = ["Your Name "] +edition = "2018" + +[dependencies] +checkerlib = { git = "https://github.com/siccegge/ctf-gameserver" } + diff --git a/examples/checker/checker_rust/src/main.rs b/examples/checker/checker_rust/src/main.rs new file mode 100644 index 0000000..a8d8dfe --- /dev/null +++ b/examples/checker/checker_rust/src/main.rs @@ -0,0 +1,34 @@ +use checkerlib::{Checker, Error, Context, CheckerResult}; + + +struct TrivialChecker { + context: Context +} + +impl TrivialChecker { + fn new(context:Context) -> Self { + Self { context: context } + } +} + +impl Checker for TrivialChecker { + + fn place_flag(&mut self) -> Result<(), Error> { + self.context.store_data("test", &String::from("test"))?; + Ok(()) + } + + fn check_flag(&mut self, tick:u32) -> Result<(), Error> { + let _s:String = self.context.load_data("test")?; + Ok(()) + } + + fn check_service(&mut self) -> Result<(), Error> { + Err(Error::CheckerError(CheckerResult::Faulty)) + } +} + + +pub fn main() { + checkerlib::run_check(TrivialChecker::new); +} diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..d243bd2 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "checkerlib" +version = "0.1.0" +authors = ["Christoph Egger "] +edition = "2018" +license = "ISC" +homepage = "https://ctf-gameserver.org/" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +base64 = "0.13" +log = { version = "0.4", features = ["std", "serde"] } +env_logger = "0.8" diff --git a/rust/examples/trivialchecker.rs b/rust/examples/trivialchecker.rs new file mode 100644 index 0000000..a8d8dfe --- /dev/null +++ b/rust/examples/trivialchecker.rs @@ -0,0 +1,34 @@ +use checkerlib::{Checker, Error, Context, CheckerResult}; + + +struct TrivialChecker { + context: Context +} + +impl TrivialChecker { + fn new(context:Context) -> Self { + Self { context: context } + } +} + +impl Checker for TrivialChecker { + + fn place_flag(&mut self) -> Result<(), Error> { + self.context.store_data("test", &String::from("test"))?; + Ok(()) + } + + fn check_flag(&mut self, tick:u32) -> Result<(), Error> { + let _s:String = self.context.load_data("test")?; + Ok(()) + } + + fn check_service(&mut self) -> Result<(), Error> { + Err(Error::CheckerError(CheckerResult::Faulty)) + } +} + + +pub fn main() { + checkerlib::run_check(TrivialChecker::new); +} diff --git a/rust/src/context.rs b/rust/src/context.rs new file mode 100644 index 0000000..2c0cff1 --- /dev/null +++ b/rust/src/context.rs @@ -0,0 +1,100 @@ +use crate::error::Error; +use std::net::IpAddr; +use std::sync::{Arc, Mutex}; + +use crate::ipc::ControlInterface; + +#[derive(Clone)] +pub struct Context { + pub ip: IpAddr, + pub team: u32, + pub tick: u32, + ctrl: Arc>, + pub (crate) local: bool, +} + + +impl Context { + pub (crate) fn new() -> Self { + use std::str::FromStr; + + let mut args = std::env::args(); + let _ = args.next(); + let ip = IpAddr::from_str(&args.next().expect("Caller needs to provide an IP address")).expect("IP address invalid"); + let team = u32::from_str( &args.next().expect("Caller needs to provide a team ID")) .expect("team ID must be an integer"); + let tick = u32::from_str( &args.next().expect("Caller needs to provide a tick")) .expect("tick must be an integer"); + + let (local, ctrl) = get_interface(format!("_{}_state.json", team)); + + Context { ip:ip, team:team, tick:tick, ctrl:Arc::new(Mutex::new(ctrl)), local:local } + } + + pub fn get_flag(&self, payload:&Vec) -> Result { + match &mut *self.ctrl.lock().unwrap() { + Ctrl::Local(ctrl) => + ctrl.get_flag(self.tick, payload), + Ctrl::Ipc(ctrl) => + ctrl.get_flag(self.tick, payload) + } + } + + + pub fn store_data (&self, key:&str, data:&S) -> Result<(), Error> { + match &mut *self.ctrl.lock().unwrap() { + Ctrl::Local(ctrl) => + ctrl.store_data(key, data), + Ctrl::Ipc(ctrl) => + ctrl.store_data(key, data) + } + } + + pub fn load_data (&self, key:&str) -> Result { + match &mut *self.ctrl.lock().unwrap() { + Ctrl::Local(ctrl) => + ctrl.load_data(key), + Ctrl::Ipc(ctrl) => + ctrl.load_data(key) + } + } + + + pub fn send_log(&self, record:&log::Record) { + match &mut *self.ctrl.lock().unwrap() { + Ctrl::Local(ctrl) => + ctrl.send_log(record), + Ctrl::Ipc(ctrl) => + ctrl.send_log(record) + } + } + + + pub fn store_result(&self, result:&crate::CheckerResult) -> Result<(), Error> { + match &mut *self.ctrl.lock().unwrap() { + Ctrl::Local(ctrl) => + ctrl.store_result(result), + Ctrl::Ipc(ctrl) => + ctrl.store_result(result) + } + } +} + +enum Ctrl { + Local(crate::local::LocalControlInterface), + Ipc(crate::ipc::IpcControlInterface) +} + +fn get_interface(fname:String) -> (bool, Ctrl) { + use std::env::VarError; + let islocal = match std::env::var("CTF_CHECKERSCRIPT") { + Ok(_s) => false, + Err(VarError::NotUnicode(_s)) => false, + Err(VarError::NotPresent) => true + }; + if islocal { + (islocal, Ctrl::Local(crate::local::LocalControlInterface::new(fname))) + } else { + (islocal, Ctrl::Ipc(crate::ipc::IpcControlInterface::new())) + } +} + + diff --git a/rust/src/error.rs b/rust/src/error.rs new file mode 100644 index 0000000..0fa12a9 --- /dev/null +++ b/rust/src/error.rs @@ -0,0 +1,29 @@ + +#[derive(Debug)] +pub enum Error { + IoError(std::io::Error), + SerdeError(serde_json::Error), + CheckerError(crate::CheckerResult), + NoSuchItem, +} + + +impl From for Error { + fn from(error: std::io::Error) -> Self { + Error::IoError(error) + } +} + + +impl From for Error { + fn from(error: serde_json::Error) -> Self { + Error::SerdeError(error) + } +} + + +impl From for Error { + fn from(error: crate::CheckerResult) -> Self { + Error::CheckerError(error) + } +} diff --git a/rust/src/flag.rs b/rust/src/flag.rs new file mode 100644 index 0000000..406d537 --- /dev/null +++ b/rust/src/flag.rs @@ -0,0 +1,8 @@ + + + + +pub fn gen_flag(_tick:u32, _payload:&Vec) -> String { + String::from("FAUST_") +} + diff --git a/rust/src/ipc.rs b/rust/src/ipc.rs new file mode 100644 index 0000000..2e75e89 --- /dev/null +++ b/rust/src/ipc.rs @@ -0,0 +1,143 @@ +use crate::Error; +use std::io::Write; +use std::io::BufRead; +use std::io::BufReader; + +use std::fs::File; +use std::os::unix::io::FromRawFd; + +use serde::{Deserialize, Serialize, de::DeserializeOwned}; + + +pub trait ControlInterface { + fn setup () -> Result<(), Error>; + fn get_flag (&mut self, tick:u32, payload:&Vec) -> Result; + fn store_data (&mut self, key:&str, data:&S) -> Result<(), Error>; + fn load_data (&mut self, key:&str) -> Result; + fn send_log(&mut self, record:&log::Record); + fn store_result(&mut self, result:&crate::CheckerResult) -> Result<(), Error>; +} + + +pub struct IpcControlInterface { + input: BufReader, + output: File +} + + +impl IpcControlInterface { + pub fn new() -> IpcControlInterface { + let infile = unsafe {std::fs::File::from_raw_fd(3)} ; + let outfile = unsafe {std::fs::File::from_raw_fd(4)} ; + IpcControlInterface { input: BufReader::new(infile), + output: outfile } + } + + fn send(&mut self, key:&str, data:&S) -> Result<(), Error> { + let json = SendMessage {action: key.to_string(), param: data }; + self.output.write(serde_json::to_string(&json)?.as_bytes())?; + Ok(()) + } + + + fn communicate(&mut self, key:&str, data:&S) -> Result { + let mut resp = String::new(); + let json = SendMessage {action: key.to_string(), param: data }; + + self.output.write(serde_json::to_string(&json)?.as_bytes())?; + self.input.read_line(&mut resp)?; + + let r:ReceiveMessage = serde_json::from_reader(resp.as_bytes())?; + Ok(r.response) + } +} + + +impl ControlInterface for IpcControlInterface { + fn setup() -> Result<(), Error> { + Ok(()) + } + + + fn get_flag(&mut self, tick:u32, payload:&Vec) -> Result { + let response:String = + self.communicate("FLAG", &SendMessageGetFlag { tick: tick, payload: base64::encode(payload.as_slice()) } )?; + Ok(response) + } + + + fn store_data (&mut self, key:&str, data:&S) -> Result<(), Error> { + let payload = serde_json::to_string(data)?; + self.send("STORE", &SendMessageStore { key: key.to_string(), data: payload } )?; + Ok(()) + } + + + fn load_data (&mut self, key:&str) -> Result { + let response:String = self.communicate("LOAD", &key.to_string())?; + Ok(serde_json::from_reader(response.as_bytes())?) + } + + + fn send_log(&mut self, record:&log::Record) { + self.send("LOG", &SendMessageLog::from(record)).unwrap() + } + + + fn store_result(&mut self, result:&crate::CheckerResult) -> Result<(), Error> { + self.send("RESULT", result)?; + Ok(()) + } +} + +impl From<&::log::Record<'_>> for SendMessageLog { + fn from(record: &::log::Record) -> Self { + SendMessageLog { + level: match record.level() { + ::log::Level::Error => 40, + ::log::Level::Warn => 30, + ::log::Level::Info => 20, + ::log::Level::Debug => 10, + ::log::Level::Trace => 5, + }, + message: format!("{}", record.args()), + funcName: record.module_path().unwrap_or("").to_string(), + pathname: record.file().unwrap_or("").to_string(), + lineno: record.line().unwrap_or(0) + } + } +} + + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct SendMessageStore { + key: String, + data: String +} + +#[allow(non_snake_case)] +#[derive(Debug, Deserialize, Serialize, Clone)] +struct SendMessageLog { + level: u32, + message: String, + funcName: String, + pathname: String, + lineno: u32 +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct SendMessageGetFlag { + payload: String, + tick: u32 +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ReceiveMessage { + response: D +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct SendMessage { + action: String, + param: D +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..b28269e --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,76 @@ +pub mod flag; +pub mod error; +pub mod context; +mod local; +mod ipc; +mod log; + +pub use error::Error; +pub use context::Context; +use serde::Serialize; + + + +#[derive(Debug, Serialize, Clone)] +pub enum CheckerResult { + Ok, + Down, + Faulty, + FlagNotFound, + Recovering +} + + +pub trait Checker { + fn place_flag(&mut self) -> Result<(), Error>; + + fn check_flag(&mut self, tick:u32) -> Result<(), Error>; + + fn check_service(&mut self) -> Result<(), Error>; +} + + +impl From for String { + fn from(r:CheckerResult) -> Self { + String::from(match r { + CheckerResult::Ok => "OK", + CheckerResult::Down => "DOWN", + CheckerResult::Faulty => "FAULTY", + CheckerResult::FlagNotFound => "FLAG_NOT_FOUND", + CheckerResult::Recovering => "RECOVERING" + }) + } +} + + +pub fn run_check (gen_checker: fn(Context) -> C) -> () { + let context = Context::new(); + let logger = crate::log::ControlLogger::new(context.clone()); + + if context.local { + env_logger::init(); + } else { + ::log::set_boxed_logger(Box::new(logger)).unwrap(); + } + + let mut checker = gen_checker(context.clone()); + + let result = + || -> Result<(), Error> { + + checker.place_flag()?; + for i in 0..5 { + checker.check_flag(i)?; + } + checker.check_service()?; + Ok(()) + }(); + + match result { + Err(error::Error::CheckerError(c)) => + context.store_result(&c).unwrap(), + Ok(()) => + context.store_result(&CheckerResult::Ok).unwrap(), + Err(_) => result.unwrap() + } +} diff --git a/rust/src/local.rs b/rust/src/local.rs new file mode 100644 index 0000000..2bfbebc --- /dev/null +++ b/rust/src/local.rs @@ -0,0 +1,80 @@ +use log::warn; + +use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use crate::ipc::ControlInterface; + +use crate::Error; + +use std::collections::HashMap; + +use std::io::SeekFrom; +use std::io::Seek; +use std::fs::{File, OpenOptions}; + + +pub struct LocalControlInterface { + localstore: File +} + + +impl LocalControlInterface { + pub fn new(fname:String) -> Self { + LocalControlInterface { localstore: OpenOptions::new().read(true).write(true).create(true).open(fname).unwrap() } + } +} + +#[derive(Serialize, Deserialize)] +struct Store { + data: HashMap +} + + +impl ControlInterface for LocalControlInterface { + fn setup() -> Result<(), Error> { + Ok(()) + } + + + fn get_flag(&mut self, tick:u32, payload:&Vec) -> Result { + Ok(crate::flag::gen_flag(tick, payload)) + } + + + fn store_data (&mut self, key:&str, data:&S) -> Result<(), Error> { + self.localstore.seek(SeekFrom::Start(0))?; + let mut store:Store = + match serde_json::from_reader(&self.localstore) { + Ok(s) => s, + Err(ref e) + if e.classify() == serde_json::error::Category::Eof + => Store {data: HashMap::new()}, + e => e? + }; + + let payload = serde_json::to_string(data)?; + store.data.insert(key.to_string(), payload); + self.localstore.seek(SeekFrom::Start(0))?; + serde_json::to_writer(&self.localstore, &store)?; + Ok(()) + } + + + fn load_data (&mut self, key:&str) -> Result { + self.localstore.seek(SeekFrom::Start(0))?; + let store:Store = serde_json::from_reader(&self.localstore)?; + let response = store.data.get(key); + match response { + Some(resp) => Ok(serde_json::from_reader(resp.as_bytes())?), + None => Err(crate::Error::NoSuchItem) + } + } + + + fn send_log(&mut self, _record:&log::Record) {} + + + fn store_result(&mut self, result:&crate::CheckerResult) -> Result<(), Error> { + warn!("Checker Result {:?}", result); + Ok(()) + } +} diff --git a/rust/src/log.rs b/rust/src/log.rs new file mode 100644 index 0000000..9128805 --- /dev/null +++ b/rust/src/log.rs @@ -0,0 +1,25 @@ +use log::{Record, Level, Metadata}; + +pub (crate) struct ControlLogger { + context: crate::Context +} + +impl ControlLogger { + pub fn new(context:crate::Context) -> Self { + ControlLogger {context: context} + } +} + +impl log::Log for ControlLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Info + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + self.context.send_log(record) + } + } + + fn flush(&self) {} +} diff --git a/rust/tests/trivial.rs b/rust/tests/trivial.rs new file mode 100644 index 0000000..5dfd354 --- /dev/null +++ b/rust/tests/trivial.rs @@ -0,0 +1,29 @@ +use checkerlib::{Checker, Error, Context, CheckerResult}; + + +struct TrivialChecker { + +} + +impl TrivialChecker { + fn new() -> Self { + TrivialChecker {} + } +} + +impl Checker for TrivialChecker { + fn setup(&mut self, context:&Context) -> () {} + + fn place_flag(&mut self) -> Result<(), Error> { Err(Error::CheckerError(CheckerResult::FlagNotFound)) } + + fn check_flag(&mut self, tick:u32) -> Result<(), Error> {Ok(())} + + fn check_service(&mut self) -> Result<(), Error> {Ok(())} +} + +#[test] +fn trivial() { + let mut checker = TrivialChecker::new(); + checkerlib::run_check(&mut checker); + assert_eq!(true, true); +}