Skip to content

Commit

Permalink
remove conversion from CLI structs to an intermediary struct and buil…
Browse files Browse the repository at this point in the history
…d context object directly from command options + sudoers file
  • Loading branch information
squell committed Feb 11, 2025
1 parent f5a960e commit 426b310
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 204 deletions.
160 changes: 97 additions & 63 deletions src/common/context.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,14 @@
use crate::common::{HARDENED_ENUM_VALUE_0, HARDENED_ENUM_VALUE_1, HARDENED_ENUM_VALUE_2};
use crate::sudo::{SudoListOptions, SudoRunOptions, SudoValidateOptions};
use crate::sudoers::Sudoers;
use crate::system::{Group, Hostname, Process, User};

use super::resolve::CurrentUser;
use super::{
command::CommandAndArguments,
resolve::{resolve_launch_and_shell, resolve_target_user_and_group},
Error, SudoPath, SudoString,
resolve::{resolve_shell, resolve_target_user_and_group, CurrentUser},
Error, SudoPath,
};

#[derive(Clone, Copy)]
pub enum ContextAction {
List,
Run,
Validate,
}

// this is a bit of a hack to keep the existing `Context` API working
#[derive(Clone)]
pub struct OptionsForContext {
pub chdir: Option<SudoPath>,
pub group: Option<SudoString>,
pub login: bool,
pub non_interactive: bool,
pub positional_args: Vec<String>,
pub reset_timestamp: bool,
pub shell: bool,
pub stdin: bool,
pub user: Option<SudoString>,
pub action: ContextAction,
}

#[derive(Debug)]
pub struct Context {
// cli options
Expand All @@ -50,7 +29,7 @@ pub struct Context {
pub password_feedback: bool,
}

#[derive(Debug, Default, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(u32)]
pub enum LaunchType {
#[default]
Expand All @@ -60,7 +39,59 @@ pub enum LaunchType {
}

impl Context {
pub fn build_from_options(sudo_options: OptionsForContext) -> Result<Context, Error> {
pub fn from_run_opts(
sudo_options: SudoRunOptions,
policy: &mut Sudoers,
) -> Result<Context, Error> {
let hostname = Hostname::resolve();
let current_user = CurrentUser::resolve()?;

let (target_user, target_group) =
resolve_target_user_and_group(&sudo_options.user, &sudo_options.group, &current_user)?;

let launch = if sudo_options.login {
LaunchType::Login
} else if sudo_options.shell {
LaunchType::Shell
} else {
LaunchType::Direct
};

let shell = resolve_shell(launch, &current_user, &target_user);

let override_path = policy.search_path(&hostname, &current_user, &target_user);

let command = {
let system_path;

let path = if let Some(path) = override_path {
path
} else {
system_path = std::env::var("PATH").unwrap_or_default();
system_path.as_ref()
};

CommandAndArguments::build_from_args(shell, sudo_options.positional_args, path)
};

Ok(Context {
hostname,
command,
current_user,
target_user,
target_group,
use_session_records: !sudo_options.reset_timestamp,
launch,
chdir: sudo_options.chdir,
stdin: sudo_options.stdin,
non_interactive: sudo_options.non_interactive,
process: Process::new(),
use_pty: true,
password_feedback: false,
})
}

pub fn from_validate_opts(sudo_options: SudoValidateOptions) -> Result<Context, Error> {
let hostname = Hostname::resolve();
let current_user = CurrentUser::resolve()?;
let (target_user, target_group) =
Expand All @@ -74,7 +105,7 @@ impl Context {
target_group,
use_session_records: !sudo_options.reset_timestamp,
launch: Default::default(),
chdir: sudo_options.chdir,
chdir: None,
stdin: sudo_options.stdin,
non_interactive: sudo_options.non_interactive,
process: Process::new(),
Expand All @@ -83,38 +114,46 @@ impl Context {
})
}

pub fn supply_command(
self,
sudo_options: OptionsForContext,
secure_path: Option<&str>,
pub fn from_list_opts(
sudo_options: SudoListOptions,
policy: &mut Sudoers,
) -> Result<Context, Error> {
let (launch, shell) =
resolve_launch_and_shell(&sudo_options, &self.current_user, &self.target_user);

let command = match sudo_options.action {
ContextAction::Run | ContextAction::List
if !sudo_options.positional_args.is_empty() =>
{
let system_path;

let path = if let Some(path) = secure_path {
path
} else {
system_path = std::env::var("PATH").unwrap_or_default();
system_path.as_ref()
};

CommandAndArguments::build_from_args(shell, sudo_options.positional_args, path)
}

// FIXME `Default` is being used as `Option::None`
_ => Default::default(),
let hostname = Hostname::resolve();
let current_user = CurrentUser::resolve()?;
let (target_user, target_group) =
resolve_target_user_and_group(&sudo_options.user, &sudo_options.group, &current_user)?;

let override_path = policy.search_path(&hostname, &current_user, &target_user);

let command = if sudo_options.positional_args.is_empty() {
Default::default()
} else {
let system_path;

let path = if let Some(path) = override_path {
path
} else {
system_path = std::env::var("PATH").unwrap_or_default();
system_path.as_ref()
};

CommandAndArguments::build_from_args(None, sudo_options.positional_args, path)
};

Ok(Self {
launch,
Ok(Context {
hostname,
command,
..self
current_user,
target_user,
target_group,
use_session_records: !sudo_options.reset_timestamp,
launch: Default::default(),
chdir: None,
stdin: sudo_options.stdin,
non_interactive: sudo_options.non_interactive,
process: Process::new(),
use_pty: true,
password_feedback: false,
})
}
}
Expand All @@ -125,23 +164,18 @@ mod tests {
sudo::SudoAction,
system::{interface::UserId, Hostname},
};
use std::collections::HashMap;

use super::Context;

#[test]
fn test_build_context() {
fn test_build_run_context() {
let options = SudoAction::try_parse_from(["sudo", "echo", "hello"])
.unwrap()
.try_into_run()
.ok()
.unwrap();
let path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
let (ctx_opts, _pipe_opts) = options.into();
let context = Context::build_from_options(ctx_opts, Some(path)).unwrap();

let mut target_environment = HashMap::new();
target_environment.insert("SUDO_USER".to_string(), context.current_user.name.clone());
let context = Context::from_run_opts(options, &mut Default::default()).unwrap();

if cfg!(target_os = "linux") {
// this assumes /bin is a symlink on /usr/bin, like it is on modern Debian/Ubuntu
Expand Down
31 changes: 14 additions & 17 deletions src/common/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ use std::{
};

use super::SudoString;
use super::{
context::{LaunchType, OptionsForContext},
Error,
};
use super::{context::LaunchType, Error};

#[derive(PartialEq, Debug)]
enum NameOrId<'a, T: FromStr> {
Expand Down Expand Up @@ -97,21 +94,21 @@ impl ops::Deref for AuthUser {

type Shell = Option<PathBuf>;

pub(super) fn resolve_launch_and_shell(
sudo_options: &OptionsForContext,
pub(super) fn resolve_shell(
launch_type: LaunchType,
current_user: &User,
target_user: &User,
) -> (LaunchType, Shell) {
if sudo_options.login {
(LaunchType::Login, Some(target_user.shell.clone()))
} else if sudo_options.shell {
let shell = env::var("SHELL")
.map(|s| s.into())
.unwrap_or_else(|_| current_user.shell.clone());

(LaunchType::Shell, Some(shell))
} else {
(LaunchType::Direct, None)
) -> Shell {
match launch_type {
LaunchType::Login => Some(target_user.shell.clone()),

LaunchType::Shell => Some(
env::var("SHELL")
.map(|s| s.into())
.unwrap_or_else(|_| current_user.shell.clone()),
),

LaunchType::Direct => None,
}
}

Expand Down
104 changes: 0 additions & 104 deletions src/sudo/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use std::{borrow::Cow, mem};

use crate::common::context::{ContextAction, OptionsForContext};
use crate::common::{SudoPath, SudoString};

pub mod help;
Expand Down Expand Up @@ -799,106 +798,3 @@ fn reject_all(context: &str, opts: SudoOptions) -> Result<(), String> {

Ok(())
}

impl From<SudoListOptions> for OptionsForContext {
fn from(opts: SudoListOptions) -> Self {
let SudoListOptions {
group,
non_interactive,
positional_args,
reset_timestamp,
stdin,
user,

list: _,
other_user: _,
} = opts;

Self {
action: ContextAction::List,

group,
non_interactive,
positional_args,
reset_timestamp,
stdin,
user,

chdir: None,
login: false,
shell: false,
}
}
}

impl From<SudoValidateOptions> for OptionsForContext {
fn from(opts: SudoValidateOptions) -> Self {
let SudoValidateOptions {
group,
non_interactive,
reset_timestamp,
stdin,
user,
} = opts;

Self {
action: ContextAction::Validate,

group,
non_interactive,
reset_timestamp,
stdin,
user,

chdir: None,
login: false,
positional_args: vec![],
shell: false,
}
}
}

pub struct OptionsForPipeline {
pub preserve_env: PreserveEnv,
pub user_requested_env_vars: Vec<(String, String)>,
}

impl SudoRunOptions {
pub fn into(self) -> (OptionsForContext, OptionsForPipeline) {
let SudoRunOptions {
chdir,
group,
login,
non_interactive,
positional_args,
reset_timestamp,
shell,
stdin,
user,

env_var_list,
preserve_env,
} = self;

let ctx_opts = OptionsForContext {
action: ContextAction::Run,

chdir,
group,
login,
non_interactive,
positional_args,
reset_timestamp,
shell,
stdin,
user,
};

let pipe_opts = OptionsForPipeline {
preserve_env,
user_requested_env_vars: env_var_list,
};

(ctx_opts, pipe_opts)
}
}
Loading

0 comments on commit 426b310

Please sign in to comment.