Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the --prompt flag #1003

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
Add initial --prompt implementation
  • Loading branch information
bjorn3 committed Mar 3, 2025
commit d9e0d3ca4b9d338061ec7117a454f22e15ce5e50
4 changes: 4 additions & 0 deletions src/common/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct Context {
pub target_user: User,
pub target_group: Group,
pub stdin: bool,
pub prompt: Option<String>,
pub non_interactive: bool,
pub use_session_records: bool,
// system
Expand Down Expand Up @@ -84,6 +85,7 @@ impl Context {
launch,
chdir: sudo_options.chdir,
stdin: sudo_options.stdin,
prompt: sudo_options.prompt,
non_interactive: sudo_options.non_interactive,
process: Process::new(),
use_pty: true,
Expand All @@ -107,6 +109,7 @@ impl Context {
launch: Default::default(),
chdir: None,
stdin: sudo_options.stdin,
prompt: sudo_options.prompt,
non_interactive: sudo_options.non_interactive,
process: Process::new(),
use_pty: true,
Expand Down Expand Up @@ -150,6 +153,7 @@ impl Context {
launch: Default::default(),
chdir: None,
stdin: sudo_options.stdin,
prompt: sudo_options.prompt,
non_interactive: sudo_options.non_interactive,
process: Process::new(),
use_pty: true,
Expand Down
17 changes: 13 additions & 4 deletions src/pam/converse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,19 @@ fn handle_message<C: Converser>(
if app_data.no_interact {
return Err(PamError::InteractionRequired);
}
let final_prompt = match app_data.auth_prompt.as_deref().unwrap_or("authenticate") {
"" => {
// Suppress password prompt entirely when -p '' is passed.
String::new()
}
prompt => {
// FIXME handle %H, %h, %p, %U, %u and %%
format!("[{}: {prompt}] {msg}", app_data.converser_name)
}
};
app_data
.converser
.handle_hidden_prompt(&format!(
"[{}: authenticate] {msg}",
app_data.converser_name
))
.handle_hidden_prompt(&final_prompt)
.map(Some)
}
ErrorMessage => app_data.converser.handle_error(msg).map(|()| None),
Expand Down Expand Up @@ -138,6 +145,7 @@ pub(super) struct ConverserData<C> {
pub(super) converser: C,
pub(super) converser_name: String,
pub(super) no_interact: bool,
pub(super) auth_prompt: Option<String>,
pub(super) panicked: bool,
}

Expand Down Expand Up @@ -361,6 +369,7 @@ mod test {
converser: "tux".to_string(),
converser_name: "tux".to_string(),
no_interact: false,
auth_prompt: None,
panicked: false,
});
let cookie = PamConvBorrow::new(hello.as_mut());
Expand Down
2 changes: 2 additions & 0 deletions src/pam/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ impl PamContext {
use_stdin: bool,
no_interact: bool,
password_feedback: bool,
auth_prompt: Option<String>,
target_user: Option<&str>,
) -> PamResult<PamContext> {
let converser = CLIConverser {
Expand All @@ -69,6 +70,7 @@ impl PamContext {
converser,
converser_name: converser_name.to_owned(),
no_interact,
auth_prompt,
panicked: false,
}));

Expand Down
2 changes: 1 addition & 1 deletion src/su/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn authenticate(requesting_user: &str, user: &str, login: bool) -> Result<PamCon
"su"
};
let use_stdin = true;
let mut pam = PamContext::new_cli("su", context, use_stdin, false, false, Some(user))?;
let mut pam = PamContext::new_cli("su", context, use_stdin, false, false, None, Some(user))?;
pam.set_requesting_user(requesting_user)?;

// attempt to set the TTY this session is communicating on
Expand Down
36 changes: 33 additions & 3 deletions src/sudo/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ pub struct SudoValidateOptions {
pub non_interactive: bool,
// -S
pub stdin: bool,
// -p
pub prompt: Option<String>,
// -g
pub group: Option<SudoString>,
// -u
Expand All @@ -190,6 +192,7 @@ impl TryFrom<SudoOptions> for SudoValidateOptions {
let reset_timestamp = mem::take(&mut opts.reset_timestamp);
let non_interactive = mem::take(&mut opts.non_interactive);
let stdin = mem::take(&mut opts.stdin);
let prompt = mem::take(&mut opts.prompt);
let group = mem::take(&mut opts.group);
let user = mem::take(&mut opts.user);

Expand All @@ -199,6 +202,7 @@ impl TryFrom<SudoOptions> for SudoValidateOptions {
reset_timestamp,
non_interactive,
stdin,
prompt,
group,
user,
})
Expand All @@ -214,6 +218,8 @@ pub struct SudoEditOptions {
pub non_interactive: bool,
// -S
pub stdin: bool,
// -p
pub prompt: Option<String>,
// -D
pub chdir: Option<SudoPath>,
// -g
Expand All @@ -234,6 +240,7 @@ impl TryFrom<SudoOptions> for SudoEditOptions {
let reset_timestamp = mem::take(&mut opts.reset_timestamp);
let non_interactive = mem::take(&mut opts.non_interactive);
let stdin = mem::take(&mut opts.stdin);
let prompt = mem::take(&mut opts.prompt);
let chdir = mem::take(&mut opts.chdir);
let group = mem::take(&mut opts.group);
let user = mem::take(&mut opts.user);
Expand All @@ -249,6 +256,7 @@ impl TryFrom<SudoOptions> for SudoEditOptions {
reset_timestamp,
non_interactive,
stdin,
prompt,
chdir,
group,
user,
Expand All @@ -268,6 +276,8 @@ pub struct SudoListOptions {
pub non_interactive: bool,
// -S
pub stdin: bool,
// -p
pub prompt: Option<String>,
// -g
pub group: Option<SudoString>,
// -U
Expand All @@ -286,6 +296,7 @@ impl TryFrom<SudoOptions> for SudoListOptions {
let reset_timestamp = mem::take(&mut opts.reset_timestamp);
let non_interactive = mem::take(&mut opts.non_interactive);
let stdin = mem::take(&mut opts.stdin);
let prompt = mem::take(&mut opts.prompt);
let group = mem::take(&mut opts.group);
let other_user = mem::take(&mut opts.other_user);
let user = mem::take(&mut opts.user);
Expand All @@ -306,6 +317,7 @@ impl TryFrom<SudoOptions> for SudoListOptions {
reset_timestamp,
non_interactive,
stdin,
prompt,
group,
other_user,
user,
Expand All @@ -324,6 +336,8 @@ pub struct SudoRunOptions {
pub non_interactive: bool,
// -S
pub stdin: bool,
// -p
pub prompt: Option<String>,
// -D
pub chdir: Option<SudoPath>,
// -g
Expand All @@ -347,6 +361,7 @@ impl TryFrom<SudoOptions> for SudoRunOptions {
let reset_timestamp = mem::take(&mut opts.reset_timestamp);
let non_interactive = mem::take(&mut opts.non_interactive);
let stdin = mem::take(&mut opts.stdin);
let prompt = mem::take(&mut opts.prompt);
let chdir = mem::take(&mut opts.chdir);
let group = mem::take(&mut opts.group);
let user = mem::take(&mut opts.user);
Expand Down Expand Up @@ -381,6 +396,7 @@ impl TryFrom<SudoOptions> for SudoRunOptions {
reset_timestamp,
non_interactive,
stdin,
prompt,
chdir,
group,
user,
Expand Down Expand Up @@ -410,6 +426,8 @@ struct SudoOptions {
shell: bool,
// -S
stdin: bool,
// -p
prompt: Option<String>,
// -u
user: Option<SudoString>,

Expand Down Expand Up @@ -480,9 +498,16 @@ enum SudoArg {
}

impl SudoArg {
const TAKES_ARGUMENT_SHORT: &'static [char] = &['D', 'g', 'h', 'R', 'U', 'u'];
const TAKES_ARGUMENT: &'static [&'static str] =
&["chdir", "group", "host", "chroot", "other-user", "user"];
const TAKES_ARGUMENT_SHORT: &'static [char] = &['D', 'g', 'h', 'p', 'R', 'U', 'u'];
const TAKES_ARGUMENT: &'static [&'static str] = &[
"chdir",
"group",
"host",
"chroot",
"other-user",
"user",
"prompt",
];

/// argument assignments and shorthand options preprocessing
fn normalize_arguments<I>(iter: I) -> Result<Vec<Self>, String>
Expand Down Expand Up @@ -667,6 +692,9 @@ impl SudoOptions {
"-g" | "--group" => {
options.group = Some(SudoString::from_cli_string(value));
}
"-p" | "--prompt" => {
options.prompt = Some(value);
}
"-U" | "--other-user" => {
options.other_user = Some(SudoString::from_cli_string(value));
}
Expand Down Expand Up @@ -759,6 +787,7 @@ fn reject_all(context: &str, opts: SudoOptions) -> Result<(), String> {
preserve_env,
shell,
stdin,
prompt,
user,
env_var_list,
edit,
Expand All @@ -785,6 +814,7 @@ fn reject_all(context: &str, opts: SudoOptions) -> Result<(), String> {
tuple!(reset_timestamp),
tuple!(shell),
tuple!(stdin),
tuple!(prompt),
tuple!(user),
tuple!(validate),
tuple!(version),
Expand Down
10 changes: 5 additions & 5 deletions src/sudo/env/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,9 @@ fn parse_env_commands(input: &str) -> Vec<(&str, Environment)> {
.collect()
}

fn create_test_context(sudo_options: &SudoRunOptions) -> Context {
fn create_test_context(sudo_options: SudoRunOptions) -> Context {
let path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".to_string();
let command =
CommandAndArguments::build_from_args(None, sudo_options.positional_args.clone(), &path);
let command = CommandAndArguments::build_from_args(None, sudo_options.positional_args, &path);

let current_user = CurrentUser::fake(User {
uid: UserId::new(1000),
Expand Down Expand Up @@ -130,8 +129,9 @@ fn create_test_context(sudo_options: &SudoRunOptions) -> Context {
root_group
},
launch: crate::common::context::LaunchType::Direct,
chdir: sudo_options.chdir.clone(),
chdir: sudo_options.chdir,
stdin: sudo_options.stdin,
prompt: sudo_options.prompt,
non_interactive: sudo_options.non_interactive,
process: Process::new(),
use_session_records: false,
Expand Down Expand Up @@ -160,7 +160,7 @@ fn test_environment_variable_filtering() {
.ok()
.unwrap();
let settings = crate::defaults::Settings::default();
let context = create_test_context(&options);
let context = create_test_context(options);
let resulting_env = get_target_environment(
initial_env.clone(),
HashMap::new(),
Expand Down
2 changes: 2 additions & 0 deletions src/sudo/pam.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub(super) fn init_pam(
use_stdin: bool,
non_interactive: bool,
password_feedback: bool,
auth_prompt: Option<String>,
auth_user: &str,
requesting_user: &str,
) -> PamResult<PamContext> {
Expand All @@ -25,6 +26,7 @@ pub(super) fn init_pam(
use_stdin,
non_interactive,
password_feedback,
auth_prompt,
None,
)?;
pam.mark_silent(!is_shell && !is_login_shell);
Expand Down
1 change: 1 addition & 0 deletions src/sudo/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ fn auth_and_update_record_file(
context.stdin,
context.non_interactive,
context.password_feedback,
context.prompt.clone(),
&auth_user.name,
&context.current_user.name,
)?;
Expand Down