From: Thomas Lamprecht Date: Mon, 23 Oct 2023 08:12:12 +0000 (+0200) Subject: termproxy: split out CLI stuff to own module X-Git-Url: https://git.proxmox.com/?p=pve-xtermjs.git;a=commitdiff_plain;h=7801b96c334e2e30802afff3409c6f1647d47219 termproxy: split out CLI stuff to own module Signed-off-by: Thomas Lamprecht --- diff --git a/termproxy/src/cli.rs b/termproxy/src/cli.rs new file mode 100644 index 0000000..cc44655 --- /dev/null +++ b/termproxy/src/cli.rs @@ -0,0 +1,102 @@ +use std::ffi::OsString; +use std::os::fd::RawFd; + +use anyhow::{bail, Result}; + +const CMD_HELP: &str = "\ +Usage: proxmox-termproxy [OPTIONS] --path -- ... + +Arguments: + Port or file descriptor to listen for TCP connections + ... The command to run connected via a proxied PTY + +Options: + --authport Port to relay auth-request, default 85 + --port-as-fd Use as file descriptor. + --path ACL object path to test on. + --perm Permission to test. + -h, --help Print help +"; + +#[derive(Debug)] +pub enum PortOrFd { + Port(u16), + Fd(RawFd), +} + +impl PortOrFd { + fn from_cli(value: u64, use_as_fd: bool) -> Result { + if use_as_fd { + if value > RawFd::MAX as u64 { + bail!("FD value too big"); + } + Ok(Self::Fd(value as RawFd)) + } else { + if value > u16::MAX as u64 { + bail!("invalid port number"); + } + Ok(Self::Port(value as u16)) + } + } +} + +#[derive(Debug)] +pub struct Options { + /// The actual command to run proxied in a pseudo terminal. + pub terminal_command: Vec, + /// The port or FD that termproxy will listen on for an incoming conection + pub listen_port: PortOrFd, + /// The port of the local privileged daemon that authentication is relayed to. Defaults to `85` + pub api_daemon_port: u16, + /// The ACL object path the 'acl_permission' is checked on + pub acl_path: String, + /// The ACL permission that the ticket, read from the stream, is required to have on 'acl_path' + pub acl_permission: Option, +} + +impl Options { + pub fn from_env() -> Result { + let mut args: Vec<_> = std::env::args_os().collect(); + args.remove(0); // remove the executable path. + + // handle finding command after `--` first so that we only parse our options later + let terminal_command = if let Some(dash_dash) = args.iter().position(|arg| arg == "--") { + let later_args = args.drain(dash_dash + 1..).collect(); + args.pop(); // .. then remove the `--` + Some(later_args) + } else { + None + }; + + // Now pass the remaining arguments through to `pico_args`. + let mut args = pico_args::Arguments::from_vec(args); + + if args.contains(["-h", "--help"]) { + print!("{CMD_HELP}"); + std::process::exit(0); + } else if terminal_command.is_none() { + bail!("missing terminal command or -- option-end marker, see '-h' for usage"); + } + + let options = Self { + terminal_command: terminal_command.unwrap(), // checked above + listen_port: PortOrFd::from_cli(args.free_from_str()?, args.contains("--port-as-fd"))?, + api_daemon_port: args.opt_value_from_str("--authport")?.unwrap_or(85), + acl_path: args.value_from_str("--path")?, + acl_permission: args.opt_value_from_str("--perm")?, + }; + + if !args.finish().is_empty() { + bail!("unexpected extra arguments, use '-h' for usage"); + } + + Ok(options) + } + + pub fn use_listen_port_as_fd(&self) -> bool { + match self.listen_port { + PortOrFd::Fd(_) => true, + _ => false, + } + } +} diff --git a/termproxy/src/main.rs b/termproxy/src/main.rs index 6962da6..cb72ef6 100644 --- a/termproxy/src/main.rs +++ b/termproxy/src/main.rs @@ -2,7 +2,6 @@ use std::cmp::min; use std::collections::HashMap; use std::ffi::OsString; use std::io::{ErrorKind, Write}; -use std::os::fd::RawFd; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::os::unix::process::CommandExt; use std::process::Command; @@ -17,6 +16,9 @@ use proxmox_io::ByteBuffer; use proxmox_lang::error::io_err_other; use proxmox_sys::linux::pty::{make_controlling_terminal, PTY}; +mod cli; +use crate::cli::{Options, PortOrFd}; + const MSG_TYPE_DATA: u8 = 0; const MSG_TYPE_RESIZE: u8 = 1; //const MSG_TYPE_PING: u8 = 2; @@ -153,7 +155,7 @@ fn authenticate(username: &[u8], ticket: &[u8], options: &Options, listen_port: // if the listen-port was passed indirectly via an FD, it's encoded also in the ticket so that // the access system can enforce that the users actually can access that port. let port_str; - if options.listen_port.is_fd() { + if options.use_listen_port_as_fd() { port_str = listen_port.to_string(); post_fields.push(("port", &port_str)); } @@ -247,104 +249,8 @@ fn run_pty<'a>(mut full_cmd: impl Iterator) -> Result const TCP: Token = Token(0); const PTY: Token = Token(1); -const CMD_HELP: &str = "\ -Usage: proxmox-termproxy [OPTIONS] --path -- ... - -Arguments: - Port or file descriptor to listen for TCP connections - ... The command to run connected via a proxied PTY - -Options: - --authport Port to relay auth-request, default 85 - --port-as-fd Use as file descriptor. - --path ACL object path to test on. - --perm Permission to test. - -h, --help Print help -"; - -#[derive(Debug)] -enum PortOrFd { - Port(u16), - Fd(RawFd), -} - -impl PortOrFd { - fn from_cli(value: u64, use_as_fd: bool) -> Result { - if use_as_fd { - if value > RawFd::MAX as u64 { - bail!("FD value too big"); - } - Ok(Self::Fd(value as RawFd)) - } else { - if value > u16::MAX as u64 { - bail!("invalid port number"); - } - Ok(Self::Port(value as u16)) - } - } - - fn is_fd(&self) -> bool { - match self { - Self::Fd(_) => true, - _ => false, - } - } -} - -#[derive(Debug)] -struct Options { - /// The actual command to run proxied in a pseudo terminal. - terminal_command: Vec, - /// The port or FD that termproxy will listen on for an incoming conection - listen_port: PortOrFd, - /// The port of the local privileged daemon that authentication is relayed to. Defaults to `85` - api_daemon_port: u16, - /// The ACL object path the 'acl_permission' is checked on - acl_path: String, - /// The ACL permission that the ticket, read from the stream, is required to have on 'acl_path' - acl_permission: Option, -} - -fn parse_args() -> Result { - let mut args: Vec<_> = std::env::args_os().collect(); - args.remove(0); // remove the executable path. - - // handle finding command after `--` first so that we only parse our options later - let terminal_command = if let Some(dash_dash) = args.iter().position(|arg| arg == "--") { - let later_args = args.drain(dash_dash + 1..).collect(); - args.pop(); // .. then remove the `--` - Some(later_args) - } else { - None - }; - - // Now pass the remaining arguments through to `pico_args`. - let mut args = pico_args::Arguments::from_vec(args); - - if args.contains(["-h", "--help"]) { - print!("{CMD_HELP}"); - std::process::exit(0); - } else if terminal_command.is_none() { - bail!("missing terminal command or -- option-end marker, see '-h' for usage"); - } - - let options = Options { - terminal_command: terminal_command.unwrap(), // checked above - listen_port: PortOrFd::from_cli(args.free_from_str()?, args.contains("--port-as-fd"))?, - api_daemon_port: args.opt_value_from_str("--authport")?.unwrap_or(85), - acl_path: args.value_from_str("--path")?, - acl_permission: args.opt_value_from_str("--perm")?, - }; - - if !args.finish().is_empty() { - bail!("unexpected extra arguments, use '-h' for usage"); - } - - Ok(options) -} - fn do_main() -> Result<()> { - let options = parse_args()?; + let options = Options::from_env()?; let (mut tcp_handle, listen_port) = listen_and_accept("localhost", &options.listen_port, Duration::new(10, 0))