diff --git a/Cargo.lock b/Cargo.lock index 3ac07b3..3a031c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1111,6 +1111,7 @@ dependencies = [ "rsjudge-judger", "rsjudge-rest", "rsjudge-runner", + "rsjudge-utils", "tokio", "toml", ] @@ -1121,13 +1122,13 @@ version = "0.1.0" dependencies = [ "anyhow", "futures", + "log", "prost", "prost-types", "rsjudge-utils", "tokio", "tokio-stream", "tonic", - "tonic-buf-build", "tonic-build", ] @@ -1156,10 +1157,11 @@ version = "0.1.0" dependencies = [ "anyhow", "caps", + "log", "nix", - "once_cell", "rsjudge-utils", "rustc_version", + "thiserror", "uzers", ] @@ -1210,12 +1212,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "semver" version = "1.0.22" @@ -1284,19 +1280,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.2.6", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sh" version = "0.2.1" @@ -1534,20 +1517,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tonic-buf-build" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89fb2d0fdd379526f80038c2bfbc5be061ff966b84b402b8131dbff4015ba25d" -dependencies = [ - "prost-build", - "scopeguard", - "serde", - "serde_yaml", - "tonic-build", - "uuid", -] - [[package]] name = "tonic-build" version = "0.11.0" @@ -1637,28 +1606,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "uuid" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" -dependencies = [ - "getrandom", - "rand", -] - [[package]] name = "uzers" version = "0.11.3" diff --git a/Cargo.toml b/Cargo.toml index 02e86d2..a2a91f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,10 @@ license = "Apache-2.0" # clap requires Rust 1.74+ to work rust-version = "1.74" +[workspace.dependencies] +log = "0.4.21" +rsjudge-utils = { version = "0.1.0", path = "crates/rsjudge-utils" } + [package] name = "rsjudge" version.workspace = true @@ -131,6 +135,7 @@ dest = "/usr/lib/sysusers.d/rsjudge.conf" # Workspace dependencies rsjudge-judger = { version = "0.1.0", path = "crates/rsjudge-judger" } rsjudge-runner = { version = "0.1.0", path = "crates/rsjudge-runner" } +rsjudge-utils.workspace = true # Optional dependencies rsjudge-grpc = { version = "0.1.0", path = "crates/rsjudge-grpc", optional = true } @@ -140,9 +145,9 @@ anyhow = "1.0.80" caps = "0.5.5" clap = { version = "4.5.3", features = ["derive"] } env_logger = "0.11.3" -log = "0.4.21" +log.workspace = true mimalloc = "0.1.39" -tokio = { version = "1.36.0", features = ["fs", "rt-multi-thread", "macros"] } +tokio = { version = "1.37.0", features = ["fs", "rt-multi-thread", "macros"] } toml = "0.8.12" # Unused for now: diff --git a/crates/rsjudge-grpc/Cargo.toml b/crates/rsjudge-grpc/Cargo.toml index b8e4857..cd48f8f 100644 --- a/crates/rsjudge-grpc/Cargo.toml +++ b/crates/rsjudge-grpc/Cargo.toml @@ -9,12 +9,12 @@ rust-version.workspace = true [dependencies] anyhow = "1.0.81" futures = "0.3.30" +log.workspace = true prost = "0.12.3" prost-types = "0.12.3" tokio = { version = "1.36.0", features = ["net"] } tokio-stream = "0.1.15" tonic = "0.11.0" -tonic-buf-build = "0.2.0" [build-dependencies] anyhow = "1.0.81" diff --git a/crates/rsjudge-grpc/src/lib.rs b/crates/rsjudge-grpc/src/lib.rs index 66d80cc..0259d2d 100644 --- a/crates/rsjudge-grpc/src/lib.rs +++ b/crates/rsjudge-grpc/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::print_stderr))] + use std::net::SocketAddr; use tonic::transport::{Error, Server}; diff --git a/crates/rsjudge-grpc/src/server.rs b/crates/rsjudge-grpc/src/server.rs index ef993d6..6274d49 100644 --- a/crates/rsjudge-grpc/src/server.rs +++ b/crates/rsjudge-grpc/src/server.rs @@ -1,3 +1,4 @@ +use log::debug; use tokio_stream::wrappers::ReceiverStream; use tonic::{async_trait, Request, Response, Status}; @@ -17,8 +18,8 @@ impl JudgeService for JudgeServerImpl { &self, request: Request, ) -> Result, Status> { - let _ = request; - todo!() + debug!("Received SelfTestRequest: {:?}", request.into_inner()); + Err(Status::unimplemented("Not implemented yet")) } type SubmitStream = ReceiverStream>; @@ -27,7 +28,7 @@ impl JudgeService for JudgeServerImpl { &self, request: Request, ) -> Result, Status> { - let _ = request; - todo!() + debug!("Received SubmitRequest: {:?}", request.into_inner()); + Err(Status::unimplemented("Not implemented yet")) } } diff --git a/crates/rsjudge-judger/src/lib.rs b/crates/rsjudge-judger/src/lib.rs index 19e251e..36d6c36 100644 --- a/crates/rsjudge-judger/src/lib.rs +++ b/crates/rsjudge-judger/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::print_stderr))] + pub mod comparer; pub use comparer::{default_comparer::DefaultComparer, CompareResult, Comparer}; diff --git a/crates/rsjudge-runner/Cargo.toml b/crates/rsjudge-runner/Cargo.toml index 0254e55..064c3fa 100644 --- a/crates/rsjudge-runner/Cargo.toml +++ b/crates/rsjudge-runner/Cargo.toml @@ -8,14 +8,15 @@ rust-version.workspace = true description = "Command runner for rsjudge" [dependencies] -anyhow = "1.0.81" caps = "0.5.5" +log.workspace = true nix = { version = "0.28.0", features = ["user"] } -once_cell = "1.19.0" +rsjudge-utils.workspace = true +thiserror = "1.0.58" uzers = "0.11.3" [build-dependencies] rustc_version = "0.4.0" [dev-dependencies] -rsjudge-utils = { path = "../rsjudge-utils" } +anyhow = "1.0.81" diff --git a/crates/rsjudge-runner/examples/demo.rs b/crates/rsjudge-runner/examples/demo.rs index 76e9b05..2811ce6 100644 --- a/crates/rsjudge-runner/examples/demo.rs +++ b/crates/rsjudge-runner/examples/demo.rs @@ -8,14 +8,14 @@ use rsjudge_runner::{ use uzers::{get_current_uid, get_user_by_uid}; fn main() -> anyhow::Result<()> { let self_output = Command::new("id") - .run_as(&get_user_by_uid(get_current_uid()).ok_or(anyhow!("invalid user"))?) + .run_as(&get_user_by_uid(get_current_uid()).ok_or(anyhow!("invalid user"))?)? .output()?; println!("{}", String::from_utf8_lossy(&self_output.stdout)); - let builder_output = Command::new("id").run_as(builder()?).output()?; + let builder_output = Command::new("id").run_as(builder()?)?.output()?; println!("{}", String::from_utf8_lossy(&builder_output.stdout)); - let runner_output = Command::new("id").run_as(runner()?).output()?; + let runner_output = Command::new("id").run_as(runner()?)?.output()?; println!("{}", String::from_utf8_lossy(&runner_output.stdout)); Ok(()) } diff --git a/crates/rsjudge-runner/examples/exploit.rs b/crates/rsjudge-runner/examples/exploit.rs index 66e8573..828710b 100644 --- a/crates/rsjudge-runner/examples/exploit.rs +++ b/crates/rsjudge-runner/examples/exploit.rs @@ -47,12 +47,12 @@ fn main() -> anyhow::Result<()> { let exploit_inner = examples.join("exploit_inner"); - let status = check_output(Command::new(dbg!(exploit_inner)).run_as(builder()?))?; + let status = check_output(Command::new(dbg!(exploit_inner)).run_as(builder()?)?)?; println!("{}", String::from_utf8_lossy(&status.stdout)); println!("{}", String::from_utf8_lossy(&status.stderr)); let normal = examples.join("normal"); - let status = check_output(Command::new(normal).run_as(builder()?))?; + let status = check_output(Command::new(normal).run_as(builder()?)?)?; println!("{}", String::from_utf8_lossy(&status.stdout)); println!("{}", String::from_utf8_lossy(&status.stderr)); diff --git a/crates/rsjudge-runner/src/error.rs b/crates/rsjudge-runner/src/error.rs new file mode 100644 index 0000000..0d477c3 --- /dev/null +++ b/crates/rsjudge-runner/src/error.rs @@ -0,0 +1,17 @@ +use caps::Capability; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("{cap} is required to run as another user.")] + CapsRequired { cap: Capability }, + #[error("User '{name}' not found")] + UserNotFound { name: String }, + + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + #[error("{0}")] + CapsError(#[from] caps::errors::CapsError), +} + +pub type Result = std::result::Result; diff --git a/crates/rsjudge-runner/src/lib.rs b/crates/rsjudge-runner/src/lib.rs index b92c4b9..9092c5d 100644 --- a/crates/rsjudge-runner/src/lib.rs +++ b/crates/rsjudge-runner/src/lib.rs @@ -1,15 +1,35 @@ +#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::print_stderr))] + use std::{os::unix::process::CommandExt as _, process::Command}; +use caps::{has_cap, CapSet, Capability}; use nix::unistd::{setgroups, Gid}; use uzers::User; +use crate::error::{Error, Result}; + +pub mod error; pub mod user; pub trait RunAs { - fn run_as(&mut self, user: &User) -> &mut Command; + type Error; + fn run_as(&mut self, user: &User) -> Result<&mut Self>; } impl RunAs for Command { - fn run_as(&mut self, user: &User) -> &mut Self { + type Error = Error; + fn run_as(&mut self, user: &User) -> Result<&mut Self> { + if !has_cap(None, CapSet::Effective, Capability::CAP_SETUID)? { + Err(Error::CapsRequired { + cap: Capability::CAP_SETUID, + })?; + } + + has_cap(None, CapSet::Effective, Capability::CAP_SETGID).map_err(|_| { + Error::CapsRequired { + cap: Capability::CAP_SETGID, + } + })?; + let uid = user.uid(); let gid = user.primary_group_id(); @@ -47,6 +67,6 @@ impl RunAs for Command { self.groups(groups); } - self + Ok(self) } } diff --git a/crates/rsjudge-runner/src/user.rs b/crates/rsjudge-runner/src/user.rs index 2182f7e..c32c5e3 100644 --- a/crates/rsjudge-runner/src/user.rs +++ b/crates/rsjudge-runner/src/user.rs @@ -1,25 +1,36 @@ -use anyhow::anyhow; -use once_cell::sync::Lazy; +use std::sync::OnceLock; + use uzers::{get_user_by_name, User}; -pub static SUPERVISOR: Lazy> = Lazy::new(|| get_user_by_name("rsjudge-supervisor")); -pub static BUILDER: Lazy> = Lazy::new(|| get_user_by_name("rsjudge-builder")); -pub static RUNNER: Lazy> = Lazy::new(|| get_user_by_name("rsjudge-runner")); +use crate::error::{Error, Result}; + +pub static SUPERVISOR: OnceLock> = OnceLock::new(); +pub static BUILDER: OnceLock> = OnceLock::new(); +pub static RUNNER: OnceLock> = OnceLock::new(); -pub fn supervisor<'a>() -> anyhow::Result<&'a User> { +pub fn supervisor<'a>() -> Result<&'a User> { SUPERVISOR + .get_or_init(|| get_user_by_name("rsjudge-supervisor")) .as_ref() - .ok_or_else(|| anyhow!("User `rsjudge-supervisor` not found")) + .ok_or_else(|| Error::UserNotFound { + name: "rsjudge-supervisor".to_string(), + }) } -pub fn builder<'a>() -> anyhow::Result<&'a User> { +pub fn builder<'a>() -> Result<&'a User> { BUILDER + .get_or_init(|| get_user_by_name("rsjudge-builder")) .as_ref() - .ok_or_else(|| anyhow!("User `rsjudge-builder` not found")) + .ok_or_else(|| Error::UserNotFound { + name: "rsjudge-builder".to_string(), + }) } -pub fn runner<'a>() -> anyhow::Result<&'a User> { +pub fn runner<'a>() -> Result<&'a User> { RUNNER + .get_or_init(|| get_user_by_name("rsjudge-runner")) .as_ref() - .ok_or_else(|| anyhow!("User `rsjudge-runner` not found")) + .ok_or_else(|| Error::UserNotFound { + name: "rsjudge-runner".to_string(), + }) } diff --git a/crates/rsjudge-utils/src/lib.rs b/crates/rsjudge-utils/src/lib.rs index a621fce..94351b8 100644 --- a/crates/rsjudge-utils/src/lib.rs +++ b/crates/rsjudge-utils/src/lib.rs @@ -1,3 +1,5 @@ +#![warn(clippy::print_stdout, clippy::print_stderr)] + //! A collection of utility functions for the rsjudge project. #![warn(missing_docs)] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1e7f748 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,48 @@ +#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::print_stderr))] + +use std::process::Command; + +use clap::Parser as _; +use env_logger::Env; +use log::{debug, info, warn}; +use rsjudge_runner::{user::builder, RunAs as _}; +use rsjudge_utils::command::display_cmd; +use tokio::fs::read; + +use crate::cli::Args; + +mod cli; + +pub async fn main_impl() -> anyhow::Result<()> { + env_logger::Builder::from_env( + Env::new() + .filter_or("RSJUDGE_LOG", "info") + .write_style("RSJUDGE_LOG_STYLE"), + ) + .format_timestamp_millis() + .format_module_path(true) + .try_init()?; + + let args = Args::try_parse()?; + debug!("{:?}", args); + + let config = read(args.config_dir.join("executors.toml")).await?; + + info!("Executing \"id\" as rsjudge-builder"); + + match Command::new("id").run_as(builder()?) { + Ok(it) => { + debug!("{} exited with {}", display_cmd(it), it.status()?); + } + Err(err) => { + warn!("Failed to run \"id\" as rsjudge-builder: {}", err); + } + }; + + debug!( + "Config:\n{:#?}", + String::from_utf8_lossy(&config).parse::()? + ); + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 5372427..a08e648 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,8 @@ -use std::process::Command; +#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::print_stderr))] -use caps::{has_cap, CapSet, Capability}; -use clap::Parser; -use env_logger::Env; -use log::{debug, info, trace}; +use log::error; use mimalloc::MiMalloc; -use rsjudge_runner::{user::builder, RunAs}; -use tokio::fs::read; - -use crate::cli::Args; +use rsjudge::main_impl; #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; @@ -17,32 +11,8 @@ mod cli; #[tokio::main] async fn main() -> anyhow::Result<()> { - env_logger::Builder::from_env( - Env::new() - .filter_or("RSJUDGE_LOG", "info") - .write_style("RSJUDGE_LOG_STYLE"), - ) - .format_timestamp_millis() - .format_module_path(true) - .try_init()?; - - let args = Args::try_parse()?; - info!("{:?}", args); - - let config = read(args.config_dir.join("executors.toml")).await?; - if has_cap(None, CapSet::Permitted, Capability::CAP_SETUID)? - && has_cap(None, CapSet::Permitted, Capability::CAP_SETGID)? - { - debug!("Executing `id` as `rsjudge-builder`"); - info!("{}", Command::new("id").run_as(builder()?).status()?); - } else { - info!("CAP_SETUID and CAP_SETGID not set, skipping."); + if let Err(err) = main_impl().await { + error!("{:?}", err); } - - trace!( - "Config:\n{:#?}", - String::from_utf8_lossy(&config).parse::()? - ); - Ok(()) }