Skip to content

Commit 426b310

Browse files
committed
remove conversion from CLI structs to an intermediary struct and build context object directly from command options + sudoers file
1 parent f5a960e commit 426b310

File tree

6 files changed

+125
-204
lines changed

6 files changed

+125
-204
lines changed

src/common/context.rs

Lines changed: 97 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,14 @@
11
use crate::common::{HARDENED_ENUM_VALUE_0, HARDENED_ENUM_VALUE_1, HARDENED_ENUM_VALUE_2};
2+
use crate::sudo::{SudoListOptions, SudoRunOptions, SudoValidateOptions};
3+
use crate::sudoers::Sudoers;
24
use crate::system::{Group, Hostname, Process, User};
35

4-
use super::resolve::CurrentUser;
56
use super::{
67
command::CommandAndArguments,
7-
resolve::{resolve_launch_and_shell, resolve_target_user_and_group},
8-
Error, SudoPath, SudoString,
8+
resolve::{resolve_shell, resolve_target_user_and_group, CurrentUser},
9+
Error, SudoPath,
910
};
1011

11-
#[derive(Clone, Copy)]
12-
pub enum ContextAction {
13-
List,
14-
Run,
15-
Validate,
16-
}
17-
18-
// this is a bit of a hack to keep the existing `Context` API working
19-
#[derive(Clone)]
20-
pub struct OptionsForContext {
21-
pub chdir: Option<SudoPath>,
22-
pub group: Option<SudoString>,
23-
pub login: bool,
24-
pub non_interactive: bool,
25-
pub positional_args: Vec<String>,
26-
pub reset_timestamp: bool,
27-
pub shell: bool,
28-
pub stdin: bool,
29-
pub user: Option<SudoString>,
30-
pub action: ContextAction,
31-
}
32-
3312
#[derive(Debug)]
3413
pub struct Context {
3514
// cli options
@@ -50,7 +29,7 @@ pub struct Context {
5029
pub password_feedback: bool,
5130
}
5231

53-
#[derive(Debug, Default, PartialEq, Eq)]
32+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5433
#[repr(u32)]
5534
pub enum LaunchType {
5635
#[default]
@@ -60,7 +39,59 @@ pub enum LaunchType {
6039
}
6140

6241
impl Context {
63-
pub fn build_from_options(sudo_options: OptionsForContext) -> Result<Context, Error> {
42+
pub fn from_run_opts(
43+
sudo_options: SudoRunOptions,
44+
policy: &mut Sudoers,
45+
) -> Result<Context, Error> {
46+
let hostname = Hostname::resolve();
47+
let current_user = CurrentUser::resolve()?;
48+
49+
let (target_user, target_group) =
50+
resolve_target_user_and_group(&sudo_options.user, &sudo_options.group, &current_user)?;
51+
52+
let launch = if sudo_options.login {
53+
LaunchType::Login
54+
} else if sudo_options.shell {
55+
LaunchType::Shell
56+
} else {
57+
LaunchType::Direct
58+
};
59+
60+
let shell = resolve_shell(launch, &current_user, &target_user);
61+
62+
let override_path = policy.search_path(&hostname, &current_user, &target_user);
63+
64+
let command = {
65+
let system_path;
66+
67+
let path = if let Some(path) = override_path {
68+
path
69+
} else {
70+
system_path = std::env::var("PATH").unwrap_or_default();
71+
system_path.as_ref()
72+
};
73+
74+
CommandAndArguments::build_from_args(shell, sudo_options.positional_args, path)
75+
};
76+
77+
Ok(Context {
78+
hostname,
79+
command,
80+
current_user,
81+
target_user,
82+
target_group,
83+
use_session_records: !sudo_options.reset_timestamp,
84+
launch,
85+
chdir: sudo_options.chdir,
86+
stdin: sudo_options.stdin,
87+
non_interactive: sudo_options.non_interactive,
88+
process: Process::new(),
89+
use_pty: true,
90+
password_feedback: false,
91+
})
92+
}
93+
94+
pub fn from_validate_opts(sudo_options: SudoValidateOptions) -> Result<Context, Error> {
6495
let hostname = Hostname::resolve();
6596
let current_user = CurrentUser::resolve()?;
6697
let (target_user, target_group) =
@@ -74,7 +105,7 @@ impl Context {
74105
target_group,
75106
use_session_records: !sudo_options.reset_timestamp,
76107
launch: Default::default(),
77-
chdir: sudo_options.chdir,
108+
chdir: None,
78109
stdin: sudo_options.stdin,
79110
non_interactive: sudo_options.non_interactive,
80111
process: Process::new(),
@@ -83,38 +114,46 @@ impl Context {
83114
})
84115
}
85116

86-
pub fn supply_command(
87-
self,
88-
sudo_options: OptionsForContext,
89-
secure_path: Option<&str>,
117+
pub fn from_list_opts(
118+
sudo_options: SudoListOptions,
119+
policy: &mut Sudoers,
90120
) -> Result<Context, Error> {
91-
let (launch, shell) =
92-
resolve_launch_and_shell(&sudo_options, &self.current_user, &self.target_user);
93-
94-
let command = match sudo_options.action {
95-
ContextAction::Run | ContextAction::List
96-
if !sudo_options.positional_args.is_empty() =>
97-
{
98-
let system_path;
99-
100-
let path = if let Some(path) = secure_path {
101-
path
102-
} else {
103-
system_path = std::env::var("PATH").unwrap_or_default();
104-
system_path.as_ref()
105-
};
106-
107-
CommandAndArguments::build_from_args(shell, sudo_options.positional_args, path)
108-
}
109-
110-
// FIXME `Default` is being used as `Option::None`
111-
_ => Default::default(),
121+
let hostname = Hostname::resolve();
122+
let current_user = CurrentUser::resolve()?;
123+
let (target_user, target_group) =
124+
resolve_target_user_and_group(&sudo_options.user, &sudo_options.group, &current_user)?;
125+
126+
let override_path = policy.search_path(&hostname, &current_user, &target_user);
127+
128+
let command = if sudo_options.positional_args.is_empty() {
129+
Default::default()
130+
} else {
131+
let system_path;
132+
133+
let path = if let Some(path) = override_path {
134+
path
135+
} else {
136+
system_path = std::env::var("PATH").unwrap_or_default();
137+
system_path.as_ref()
138+
};
139+
140+
CommandAndArguments::build_from_args(None, sudo_options.positional_args, path)
112141
};
113142

114-
Ok(Self {
115-
launch,
143+
Ok(Context {
144+
hostname,
116145
command,
117-
..self
146+
current_user,
147+
target_user,
148+
target_group,
149+
use_session_records: !sudo_options.reset_timestamp,
150+
launch: Default::default(),
151+
chdir: None,
152+
stdin: sudo_options.stdin,
153+
non_interactive: sudo_options.non_interactive,
154+
process: Process::new(),
155+
use_pty: true,
156+
password_feedback: false,
118157
})
119158
}
120159
}
@@ -125,23 +164,18 @@ mod tests {
125164
sudo::SudoAction,
126165
system::{interface::UserId, Hostname},
127166
};
128-
use std::collections::HashMap;
129167

130168
use super::Context;
131169

132170
#[test]
133-
fn test_build_context() {
171+
fn test_build_run_context() {
134172
let options = SudoAction::try_parse_from(["sudo", "echo", "hello"])
135173
.unwrap()
136174
.try_into_run()
137175
.ok()
138176
.unwrap();
139-
let path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
140-
let (ctx_opts, _pipe_opts) = options.into();
141-
let context = Context::build_from_options(ctx_opts, Some(path)).unwrap();
142177

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

146180
if cfg!(target_os = "linux") {
147181
// this assumes /bin is a symlink on /usr/bin, like it is on modern Debian/Ubuntu

src/common/resolve.rs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ use std::{
1111
};
1212

1313
use super::SudoString;
14-
use super::{
15-
context::{LaunchType, OptionsForContext},
16-
Error,
17-
};
14+
use super::{context::LaunchType, Error};
1815

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

9895
type Shell = Option<PathBuf>;
9996

100-
pub(super) fn resolve_launch_and_shell(
101-
sudo_options: &OptionsForContext,
97+
pub(super) fn resolve_shell(
98+
launch_type: LaunchType,
10299
current_user: &User,
103100
target_user: &User,
104-
) -> (LaunchType, Shell) {
105-
if sudo_options.login {
106-
(LaunchType::Login, Some(target_user.shell.clone()))
107-
} else if sudo_options.shell {
108-
let shell = env::var("SHELL")
109-
.map(|s| s.into())
110-
.unwrap_or_else(|_| current_user.shell.clone());
111-
112-
(LaunchType::Shell, Some(shell))
113-
} else {
114-
(LaunchType::Direct, None)
101+
) -> Shell {
102+
match launch_type {
103+
LaunchType::Login => Some(target_user.shell.clone()),
104+
105+
LaunchType::Shell => Some(
106+
env::var("SHELL")
107+
.map(|s| s.into())
108+
.unwrap_or_else(|_| current_user.shell.clone()),
109+
),
110+
111+
LaunchType::Direct => None,
115112
}
116113
}
117114

src/sudo/cli/mod.rs

Lines changed: 0 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

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

5-
use crate::common::context::{ContextAction, OptionsForContext};
65
use crate::common::{SudoPath, SudoString};
76

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

800799
Ok(())
801800
}
802-
803-
impl From<SudoListOptions> for OptionsForContext {
804-
fn from(opts: SudoListOptions) -> Self {
805-
let SudoListOptions {
806-
group,
807-
non_interactive,
808-
positional_args,
809-
reset_timestamp,
810-
stdin,
811-
user,
812-
813-
list: _,
814-
other_user: _,
815-
} = opts;
816-
817-
Self {
818-
action: ContextAction::List,
819-
820-
group,
821-
non_interactive,
822-
positional_args,
823-
reset_timestamp,
824-
stdin,
825-
user,
826-
827-
chdir: None,
828-
login: false,
829-
shell: false,
830-
}
831-
}
832-
}
833-
834-
impl From<SudoValidateOptions> for OptionsForContext {
835-
fn from(opts: SudoValidateOptions) -> Self {
836-
let SudoValidateOptions {
837-
group,
838-
non_interactive,
839-
reset_timestamp,
840-
stdin,
841-
user,
842-
} = opts;
843-
844-
Self {
845-
action: ContextAction::Validate,
846-
847-
group,
848-
non_interactive,
849-
reset_timestamp,
850-
stdin,
851-
user,
852-
853-
chdir: None,
854-
login: false,
855-
positional_args: vec![],
856-
shell: false,
857-
}
858-
}
859-
}
860-
861-
pub struct OptionsForPipeline {
862-
pub preserve_env: PreserveEnv,
863-
pub user_requested_env_vars: Vec<(String, String)>,
864-
}
865-
866-
impl SudoRunOptions {
867-
pub fn into(self) -> (OptionsForContext, OptionsForPipeline) {
868-
let SudoRunOptions {
869-
chdir,
870-
group,
871-
login,
872-
non_interactive,
873-
positional_args,
874-
reset_timestamp,
875-
shell,
876-
stdin,
877-
user,
878-
879-
env_var_list,
880-
preserve_env,
881-
} = self;
882-
883-
let ctx_opts = OptionsForContext {
884-
action: ContextAction::Run,
885-
886-
chdir,
887-
group,
888-
login,
889-
non_interactive,
890-
positional_args,
891-
reset_timestamp,
892-
shell,
893-
stdin,
894-
user,
895-
};
896-
897-
let pipe_opts = OptionsForPipeline {
898-
preserve_env,
899-
user_requested_env_vars: env_var_list,
900-
};
901-
902-
(ctx_opts, pipe_opts)
903-
}
904-
}

0 commit comments

Comments
 (0)