Skip to content

Commit bceee7e

Browse files
shurizzleshurizzlesquell
authored
feat: add -B/--bell flag (#1051)
As discussed in #1028 I opened the PR for -B/--bell flag --------- Co-authored-by: shurizzle <me@shurizzle.dev> Co-authored-by: Marc Schoolderman <marc@tweedegolf.com>
1 parent 5d96272 commit bceee7e

File tree

11 files changed

+74
-10
lines changed

11 files changed

+74
-10
lines changed

docs/man/sudo.8.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ title: SUDO(8) sudo-rs 0.2.4 | sudo-rs
88

99
# SYNOPSIS
1010

11-
`sudo` [`-u` *user*] [`-g` *group*] [`-D` *directory*] [`-knS`] [`-i` | `-s`] [`VAR=value`] [<*command*>] \
11+
`sudo` [`-u` *user*] [`-g` *group*] [`-D` *directory*] [`-BknS`] [`-i` | `-s`] [`VAR=value`] [<*command*>] \
1212
`sudo` `-h` | `-K` | `-k` | `-V`
1313

1414
# DESCRIPTION
@@ -29,6 +29,9 @@ even if that process runs in its own pseudo terminal.
2929

3030
# OPTIONS
3131

32+
`-B`, `--bell`
33+
: Ring the bell as part of the password prompt when a terminal is present.
34+
3235
`-D` *directory*, `--chdir`=*directory*
3336
: Run the *command* in the specified *directory* instead of the current
3437
working directory. The security policy may return an error if the user does

src/common/context.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub struct Context {
2121
pub target_user: User,
2222
pub target_group: Group,
2323
pub stdin: bool,
24+
pub bell: bool,
2425
pub prompt: Option<String>,
2526
pub non_interactive: bool,
2627
pub use_session_records: bool,
@@ -88,6 +89,7 @@ impl Context {
8889
launch,
8990
chdir: sudo_options.chdir,
9091
stdin: sudo_options.stdin,
92+
bell: sudo_options.bell,
9193
prompt: sudo_options.prompt,
9294
non_interactive: sudo_options.non_interactive,
9395
process: Process::new(),
@@ -112,6 +114,7 @@ impl Context {
112114
launch: Default::default(),
113115
chdir: None,
114116
stdin: sudo_options.stdin,
117+
bell: sudo_options.bell,
115118
prompt: sudo_options.prompt,
116119
non_interactive: sudo_options.non_interactive,
117120
process: Process::new(),
@@ -156,6 +159,7 @@ impl Context {
156159
launch: Default::default(),
157160
chdir: None,
158161
stdin: sudo_options.stdin,
162+
bell: sudo_options.bell,
159163
prompt: sudo_options.prompt,
160164
non_interactive: sudo_options.non_interactive,
161165
process: Process::new(),

src/pam/converse.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ fn handle_message<C: Converser>(
9696
pub struct CLIConverser {
9797
pub(super) name: String,
9898
pub(super) use_stdin: bool,
99+
pub(super) bell: bool,
99100
pub(super) password_feedback: bool,
100101
}
101102

@@ -120,6 +121,9 @@ impl Converser for CLIConverser {
120121

121122
fn handle_hidden_prompt(&self, msg: &str) -> PamResult<PamBuffer> {
122123
let mut tty = self.open()?;
124+
if self.bell && !self.use_stdin {
125+
tty.bell()?;
126+
}
123127
tty.prompt(msg)?;
124128
if self.password_feedback {
125129
Ok(tty.read_password_with_feedback()?)

src/pam/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,13 @@ impl PamContext {
4747
converser_name: &str,
4848
service_name: &str,
4949
use_stdin: bool,
50+
bell: bool,
5051
no_interact: bool,
5152
password_feedback: bool,
5253
target_user: Option<&str>,
5354
) -> PamResult<PamContext> {
5455
let converser = CLIConverser {
56+
bell,
5557
name: converser_name.to_owned(),
5658
use_stdin,
5759
password_feedback,

src/pam/rpassword.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ fn read_unbuffered_with_feedback(
168168
}
169169

170170
/// Write something and immediately flush
171-
fn write_unbuffered(sink: &mut dyn io::Write, text: &str) -> io::Result<()> {
172-
sink.write_all(text.as_bytes())?;
171+
fn write_unbuffered(sink: &mut dyn io::Write, text: &[u8]) -> io::Result<()> {
172+
sink.write_all(text)?;
173173
sink.flush()
174174
}
175175

@@ -221,7 +221,13 @@ impl Terminal<'_> {
221221

222222
/// Display information
223223
pub fn prompt(&mut self, text: &str) -> io::Result<()> {
224-
write_unbuffered(self.sink(), text)
224+
write_unbuffered(self.sink(), text.as_bytes())
225+
}
226+
227+
/// Ring the bell
228+
pub fn bell(&mut self) -> io::Result<()> {
229+
const BELL: &[u8; 1] = b"\x07";
230+
write_unbuffered(self.sink(), BELL)
225231
}
226232

227233
// boilerplate reduction functions
@@ -269,7 +275,7 @@ mod test {
269275
#[test]
270276
fn miri_test_write() {
271277
let mut data = Vec::new();
272-
write_unbuffered(&mut data, "prompt").unwrap();
278+
write_unbuffered(&mut data, b"prompt").unwrap();
273279
assert_eq!(std::str::from_utf8(&data).unwrap(), "prompt");
274280
}
275281
}

src/su/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ fn authenticate(requesting_user: &str, user: &str, login: bool) -> Result<PamCon
2929
"su"
3030
};
3131
let use_stdin = true;
32-
let mut pam = PamContext::new_cli("su", context, use_stdin, false, false, Some(user))?;
32+
let mut pam = PamContext::new_cli("su", context, use_stdin, false, false, false, Some(user))?;
3333
pam.set_requesting_user(requesting_user)?;
3434

3535
// attempt to set the TTY this session is communicating on

src/sudo/cli/help.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
pub const USAGE_MSG: &str = "\
22
usage: sudo -h | -K | -k | -V
3-
usage: sudo -v [-knS] [-g group] [-u user]
4-
usage: sudo -l [-knS] [-g group] [-U user] [-u user] [command [arg ...]]
5-
usage: sudo [-knS] [-D directory] [-g group] [-u user] [-i | -s] [command [arg ...]]
6-
usage: sudo -e [-knS] [-D directory] [-g group] [-u user] file ...";
3+
usage: sudo -v [-BknS] [-g group] [-u user]
4+
usage: sudo -l [-BknS] [-g group] [-U user] [-u user] [command [arg ...]]
5+
usage: sudo [-BknS] [-D directory] [-g group] [-u user] [-i | -s] [command [arg ...]]
6+
usage: sudo -e [-BknS] [-D directory] [-g group] [-u user] file ...";
77

88
const DESCRIPTOR: &str = "sudo - run commands as another user";
99

1010
const HELP_MSG: &str = "Options:
11+
-B, --bell ring bell when prompting
1112
-D, --chdir=directory change the working directory before running command
1213
-g, --group=group run command as the specified group name or ID
1314
-h, --help display help message and exit

src/sudo/cli/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ impl TryFrom<SudoOptions> for SudoResetTimestampOptions {
167167

168168
// sudo -v [-ABkNnS] [-g group] [-h host] [-p prompt] [-u user]
169169
pub struct SudoValidateOptions {
170+
// -B
171+
pub bell: bool,
170172
// -k
171173
pub reset_timestamp: bool,
172174
// -n
@@ -189,16 +191,22 @@ impl TryFrom<SudoOptions> for SudoValidateOptions {
189191
let validate = mem::take(&mut opts.validate);
190192
debug_assert!(validate);
191193

194+
let bell = mem::take(&mut opts.bell);
192195
let reset_timestamp = mem::take(&mut opts.reset_timestamp);
193196
let non_interactive = mem::take(&mut opts.non_interactive);
194197
let stdin = mem::take(&mut opts.stdin);
195198
let prompt = mem::take(&mut opts.prompt);
196199
let group = mem::take(&mut opts.group);
197200
let user = mem::take(&mut opts.user);
198201

202+
if bell && stdin {
203+
return Err("--bell conflicts with --stdin".into());
204+
}
205+
199206
reject_all("--validate", opts)?;
200207

201208
Ok(Self {
209+
bell,
202210
reset_timestamp,
203211
non_interactive,
204212
stdin,
@@ -212,6 +220,8 @@ impl TryFrom<SudoOptions> for SudoValidateOptions {
212220
// sudo -e [-ABkNnS] [-r role] [-t type] [-C num] [-D directory] [-g group] [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] file ...
213221
#[allow(dead_code)]
214222
pub struct SudoEditOptions {
223+
// -B
224+
pub bell: bool,
215225
// -k
216226
pub reset_timestamp: bool,
217227
// -n
@@ -237,6 +247,7 @@ impl TryFrom<SudoOptions> for SudoEditOptions {
237247
let edit = mem::take(&mut opts.edit);
238248
debug_assert!(edit);
239249

250+
let bell = mem::take(&mut opts.bell);
240251
let reset_timestamp = mem::take(&mut opts.reset_timestamp);
241252
let non_interactive = mem::take(&mut opts.non_interactive);
242253
let stdin = mem::take(&mut opts.stdin);
@@ -246,13 +257,18 @@ impl TryFrom<SudoOptions> for SudoEditOptions {
246257
let user = mem::take(&mut opts.user);
247258
let positional_args = mem::take(&mut opts.positional_args);
248259

260+
if bell && stdin {
261+
return Err("--bell conflicts with --stdin".into());
262+
}
263+
249264
reject_all("--edit", opts)?;
250265

251266
if positional_args.is_empty() {
252267
return Err("must specify at least one file path".into());
253268
}
254269

255270
Ok(Self {
271+
bell,
256272
reset_timestamp,
257273
non_interactive,
258274
stdin,
@@ -267,6 +283,8 @@ impl TryFrom<SudoOptions> for SudoEditOptions {
267283

268284
// sudo -l [-ABkNnS] [-g group] [-h host] [-p prompt] [-U user] [-u user] [command [arg ...]]
269285
pub struct SudoListOptions {
286+
// -B
287+
pub bell: bool,
270288
// -l OR -l -l
271289
pub list: List,
272290

@@ -292,6 +310,7 @@ impl TryFrom<SudoOptions> for SudoListOptions {
292310
type Error = String;
293311

294312
fn try_from(mut opts: SudoOptions) -> Result<Self, Self::Error> {
313+
let bell = mem::take(&mut opts.bell);
295314
let list = opts.list.take().unwrap();
296315
let reset_timestamp = mem::take(&mut opts.reset_timestamp);
297316
let non_interactive = mem::take(&mut opts.non_interactive);
@@ -302,6 +321,10 @@ impl TryFrom<SudoOptions> for SudoListOptions {
302321
let user = mem::take(&mut opts.user);
303322
let positional_args = mem::take(&mut opts.positional_args);
304323

324+
if bell && stdin {
325+
return Err("--bell conflicts with --stdin".into());
326+
}
327+
305328
// when present, `-u` must be accompanied by a command
306329
let has_command = !positional_args.is_empty();
307330
let valid_user_flag = user.is_none() || has_command;
@@ -313,6 +336,7 @@ impl TryFrom<SudoOptions> for SudoListOptions {
313336
reject_all("--list", opts)?;
314337

315338
Ok(Self {
339+
bell,
316340
list,
317341
reset_timestamp,
318342
non_interactive,
@@ -328,6 +352,8 @@ impl TryFrom<SudoOptions> for SudoListOptions {
328352

329353
// sudo [-ABbEHnPS] [-C num] [-D directory] [-g group] [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] [VAR=value] [-i | -s] [command [arg ...]]
330354
pub struct SudoRunOptions {
355+
// -B
356+
pub bell: bool,
331357
// -E
332358
pub preserve_env: PreserveEnv,
333359
// -k
@@ -357,6 +383,7 @@ impl TryFrom<SudoOptions> for SudoRunOptions {
357383
type Error = String;
358384

359385
fn try_from(mut opts: SudoOptions) -> Result<Self, Self::Error> {
386+
let bell = mem::take(&mut opts.bell);
360387
let preserve_env = mem::take(&mut opts.preserve_env);
361388
let reset_timestamp = mem::take(&mut opts.reset_timestamp);
362389
let non_interactive = mem::take(&mut opts.non_interactive);
@@ -370,6 +397,10 @@ impl TryFrom<SudoOptions> for SudoRunOptions {
370397
let shell = mem::take(&mut opts.shell);
371398
let positional_args = mem::take(&mut opts.positional_args);
372399

400+
if bell && stdin {
401+
return Err("--bell conflicts with --stdin".into());
402+
}
403+
373404
let context = match (login, shell, positional_args.is_empty()) {
374405
(true, false, _) => "--login",
375406
(false, true, _) => "--shell",
@@ -392,6 +423,7 @@ impl TryFrom<SudoOptions> for SudoRunOptions {
392423
reject_all(context, opts)?;
393424

394425
Ok(Self {
426+
bell,
395427
preserve_env,
396428
reset_timestamp,
397429
non_interactive,
@@ -410,6 +442,8 @@ impl TryFrom<SudoOptions> for SudoRunOptions {
410442

411443
#[derive(Default)]
412444
struct SudoOptions {
445+
// -B
446+
bell: bool,
413447
// -D
414448
chdir: Option<SudoPath>,
415449
// -g
@@ -628,6 +662,9 @@ impl SudoOptions {
628662
for arg in arg_iter {
629663
match arg {
630664
SudoArg::Flag(flag) => match flag.as_str() {
665+
"-B" | "--bell" => {
666+
options.bell = true;
667+
}
631668
"-E" | "--preserve-env" => {
632669
options.preserve_env = PreserveEnv::Everything;
633670
}
@@ -779,6 +816,7 @@ fn reject_all(context: &str, opts: SudoOptions) -> Result<(), String> {
779816
}
780817

781818
let SudoOptions {
819+
bell,
782820
chdir,
783821
group,
784822
login,
@@ -801,6 +839,7 @@ fn reject_all(context: &str, opts: SudoOptions) -> Result<(), String> {
801839
} = opts;
802840

803841
let flags = [
842+
tuple!(bell),
804843
tuple!(chdir),
805844
tuple!(edit),
806845
tuple!(group),

src/sudo/env/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ fn create_test_context(sudo_options: SudoRunOptions) -> Context {
133133
use_session_records: false,
134134
use_pty: true,
135135
password_feedback: false,
136+
bell: false,
136137
}
137138
}
138139

src/sudo/pam.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::system::term::current_tty_name;
99
pub(super) struct InitPamArgs<'a> {
1010
pub(super) launch: LaunchType,
1111
pub(super) use_stdin: bool,
12+
pub(super) bell: bool,
1213
pub(super) non_interactive: bool,
1314
pub(super) password_feedback: bool,
1415
pub(super) auth_prompt: Option<String>,
@@ -22,6 +23,7 @@ pub(super) fn init_pam(
2223
InitPamArgs {
2324
launch,
2425
use_stdin,
26+
bell,
2527
non_interactive,
2628
password_feedback,
2729
auth_prompt,
@@ -39,6 +41,7 @@ pub(super) fn init_pam(
3941
"sudo",
4042
service_name,
4143
use_stdin,
44+
bell,
4245
non_interactive,
4346
password_feedback,
4447
None,

0 commit comments

Comments
 (0)