From 2bba6b5341fd341de2b282e43f9b62f281ccf40e Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Wed, 24 Jan 2024 16:17:20 +0100 Subject: An attempt at implementing a shell --- rs/Cargo.lock | 4 ++ rs/Cargo.toml | 1 + rs/server/src/main.rs | 11 ++-- rs/shell/Cargo.toml | 6 +++ rs/shell/src/main.rs | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 rs/shell/Cargo.toml create mode 100644 rs/shell/src/main.rs diff --git a/rs/Cargo.lock b/rs/Cargo.lock index ed8409a..afc8c7b 100644 --- a/rs/Cargo.lock +++ b/rs/Cargo.lock @@ -1660,6 +1660,10 @@ dependencies = [ "digest", ] +[[package]] +name = "shell" +version = "0.1.0" + [[package]] name = "signal-hook-registry" version = "1.4.1" diff --git a/rs/Cargo.toml b/rs/Cargo.toml index ec75eb3..6439e6b 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -4,4 +4,5 @@ members = [ "common", "git-lfs-authenticate", "server", + "shell", ] diff --git a/rs/server/src/main.rs b/rs/server/src/main.rs index 880cb46..76e447d 100644 --- a/rs/server/src/main.rs +++ b/rs/server/src/main.rs @@ -116,7 +116,8 @@ struct Env { } fn require_env(name: &str) -> Result { - std::env::var(name).map_err(|_| format!("environment variable {name} should be defined and valid")) + std::env::var(name) + .map_err(|_| format!("environment variable {name} should be defined and valid")) } impl Env { @@ -130,7 +131,8 @@ impl Env { key_path: require_env("GITOLFS3_KEY_PATH")?, listen_host: require_env("GITOLFS3_LISTEN_HOST")?, listen_port: require_env("GITOLFS3_LISTEN_PORT")?, - trusted_forwarded_hosts: std::env::var("GITOLFS3_TRUSTED_FORWARDED_HOSTS").unwrap_or_default(), + trusted_forwarded_hosts: std::env::var("GITOLFS3_TRUSTED_FORWARDED_HOSTS") + .unwrap_or_default(), }) } } @@ -170,7 +172,7 @@ async fn main() -> ExitCode { Err(e) => { println!("Failed to create S3 client: {e}"); return ExitCode::FAILURE; - }, + } }; let key = match common::load_key(&env.key_path) { Ok(key) => key, @@ -180,7 +182,8 @@ async fn main() -> ExitCode { } }; - let trusted_forwarded_hosts: HashSet = env.trusted_forwarded_hosts + let trusted_forwarded_hosts: HashSet = env + .trusted_forwarded_hosts .split(',') .map(|s| s.to_owned()) .filter(|s| !s.is_empty()) diff --git a/rs/shell/Cargo.toml b/rs/shell/Cargo.toml new file mode 100644 index 0000000..0dcb6d6 --- /dev/null +++ b/rs/shell/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "shell" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/rs/shell/src/main.rs b/rs/shell/src/main.rs new file mode 100644 index 0000000..cbe01e7 --- /dev/null +++ b/rs/shell/src/main.rs @@ -0,0 +1,138 @@ +use std::{process::ExitCode, os::unix::process::CommandExt}; + +fn parse_sq(s: &str) -> Option<(String, &str)> { + #[derive(PartialEq, Eq)] + enum SqState { + Quoted, + Unquoted { may_escape: bool }, + UnquotedEscaped, + } + + let mut result = String::new(); + let mut state = SqState::Unquoted { may_escape: false }; + let mut remaining = ""; + for (i, c) in s.char_indices() { + match state { + SqState::Unquoted { may_escape: false} => { + if c != '\'' { + return None; + } + state = SqState::Quoted + } + SqState::Quoted => { + if c == '\'' { + state = SqState::Unquoted { may_escape: true }; + continue; + } + result.push(c); + } + SqState::Unquoted { may_escape: true }=> { + if is_posix_space(c) { + remaining = &s[i..]; + break; + } + if c != '\\' { + return None; + } + state = SqState::UnquotedEscaped; + } + SqState::UnquotedEscaped => { + if c != '\\' && c != '!' { + return None; + } + result.push(c); + state = SqState::Unquoted { may_escape: false }; + } + } + } + + if state != (SqState::Unquoted { may_escape: true }) { + return None + } + Some((result, remaining)) +} + +fn parse_cmd(mut cmd: &str) -> Option> { + let mut args = Vec::::new(); + + cmd = cmd.trim_matches(is_posix_space); + while cmd != "" { + if cmd.starts_with('\'') { + let (arg, remaining) = parse_sq(cmd)?; + args.push(arg); + cmd = remaining.trim_start_matches(is_posix_space); + } else if let Some((arg, remaining)) = cmd.split_once(is_posix_space) { + args.push(arg.to_owned()); + cmd = remaining.trim_start_matches(is_posix_space); + } else { + args.push(cmd.to_owned()); + cmd = ""; + } + } + + Some(args) +} + +fn is_posix_space(c: char) -> bool { + // Form feed: 0x0c + // Vertical tab: 0x0b + c == ' ' || c == '\x0c' || c == '\n' || c == '\r' || c == '\t' || c == '\x0b' +} + +fn main() -> ExitCode { + let mut args = std::env::args().skip(1); + if args.next() != Some("-c".to_string()) { + eprintln!("Expected usage: shell -c "); + return ExitCode::FAILURE; + } + let Some(cmd) = args.next() else { + eprintln!("Missing argument for argument '-c'"); + return ExitCode::FAILURE; + }; + if args.next() != None { + eprintln!("Too many arguments passed"); + return ExitCode::FAILURE; + } + + let Some(mut cmd) = parse_cmd(&cmd) else { + eprintln!("Bad command"); + return ExitCode::FAILURE; + }; + + let Some(mut program) = cmd.drain(0..1).next() else { + eprintln!("Bad command"); + return ExitCode::FAILURE; + }; + if program == "git" { + let Some(subcommand) = cmd.drain(0..1).next() else { + eprintln!("Bad command"); + return ExitCode::FAILURE; + }; + program.push('-'); + program.push_str(&subcommand); + } + + let mut args = Vec::new(); + + let git_cmds = ["receive-pack", "upload-archive", "upload-pack"]; + if git_cmds.contains(&program.as_str()) { + if cmd.len() != 1 { + eprintln!("Bad command"); + return ExitCode::FAILURE; + } + let repository = cmd[0].trim_start_matches('/'); + args.push(repository); + } else if program == "git-lfs-authenticate" { + if cmd.len() != 2 { + eprintln!("Bad command"); + return ExitCode::FAILURE; + } + let repository = cmd[0].trim_start_matches('/'); + args.push(repository); + args.push(&cmd[1]); + } + + let e = std::process::Command::new(program).args(args).exec(); + eprintln!("Error: {e}"); + ExitCode::FAILURE +} -- cgit v1.2.3