Skip to content

Commit ba4ce21

Browse files
committed
make visudo response reader use BufRead::Lines
and accept "y" as a universal affirmative response
1 parent df01058 commit ba4ce21

File tree

3 files changed

+54
-46
lines changed

3 files changed

+54
-46
lines changed

po/sudo-rs.pot

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,12 @@ msgstr ""
325325
msgid "sudoedit: truncate {path} to zero? (y/n) [n] "
326326
msgstr ""
327327

328-
#: src/sudo/edit.rs:314
328+
#: src/sudo/edit.rs:319
329329
#, rust-format
330330
msgid "not overwriting {path}"
331331
msgstr ""
332332

333-
#: src/sudo/edit.rs:318 src/sudo/edit.rs:327
333+
#: src/sudo/edit.rs:323 src/sudo/edit.rs:332
334334
#, rust-format
335335
msgid "failed to write data to parent: {error}"
336336
msgstr ""
@@ -392,6 +392,12 @@ msgid ""
392392
" -- stop processing command line arguments"
393393
msgstr ""
394394

395+
#: src/sudo/cli/mod.rs:210 src/sudo/cli/mod.rs:275 src/sudo/cli/mod.rs:347
396+
#: src/sudo/cli/mod.rs:433 src/sudo/cli/mod.rs:446 src/sudo/cli/mod.rs:846
397+
#, rust-format
398+
msgid "{context} cannot be used together with {option}"
399+
msgstr ""
400+
395401
#: src/sudo/cli/mod.rs:284
396402
msgid "must specify at least one file path"
397403
msgstr ""
@@ -443,12 +449,6 @@ msgstr ""
443449
msgid "sudoedit doesn't need to be run via sudo"
444450
msgstr ""
445451

446-
#: src/sudo/cli/mod.rs:210 src/sudo/cli/mod.rs:275 src/sudo/cli/mod.rs:347
447-
#: src/sudo/cli/mod.rs:433 src/sudo/cli/mod.rs:446 src/sudo/cli/mod.rs:846
448-
#, rust-format
449-
msgid "{context} cannot be used together with {option}"
450-
msgstr ""
451-
452452
#: src/sudo/cli/mod.rs:897
453453
msgid "command"
454454
msgstr ""

src/sudo/edit.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -299,17 +299,22 @@ fn handle_child_inner(editor: &Path, mut files: Vec<ChildFileInfo<'_>>) -> Resul
299299
// If the file has been changed to be empty, ask the user what to do.
300300
if new_data.is_empty() && new_data != file.old_data {
301301
// TRANSLATORS: the initial letters of 'yes' and 'no' responses, in that order
302-
let answers = xlat!("yn").as_bytes().get(..2).unwrap_or(b"yn");
302+
let answers = xlat!("yn");
303303

304304
match crate::visudo::ask_response(
305-
xlat!(
305+
&xlat!(
306306
"sudoedit: truncate {path} to zero? (y/n) [n] ",
307307
path = file.path.display()
308-
)
309-
.as_bytes(),
308+
),
310309
answers,
310+
answers
311+
.chars()
312+
.last()
313+
.expect("translation files are corrupted"),
311314
) {
312-
Ok(val) if val == answers[0] => {}
315+
Ok(val) if answers.starts_with(val) => {}
316+
// a fallback: also accept 'yes' based on Debian's apt behaviour
317+
Ok('y') if !answers.contains('y') => {}
313318
_ => {
314319
user_info!("not overwriting {path}", path = file.path.display());
315320

src/visudo/mod.rs

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod help;
66
use std::{
77
env, ffi,
88
fs::{File, Permissions},
9-
io::{self, Read, Seek, Write},
9+
io::{self, BufRead, Read, Seek, Write},
1010
os::unix::prelude::{MetadataExt, PermissionsExt},
1111
path::{Path, PathBuf},
1212
process::Command,
@@ -318,8 +318,12 @@ fn edit_sudoers_file(
318318

319319
writeln!(stderr)?;
320320

321-
match ask_response(b"What now? e(x)it without saving / (e)dit again: ", b"xe")? {
322-
b'x' => return Ok(()),
321+
match ask_response(
322+
"What now? e(x)it without saving / (e)dit again: ",
323+
"xe",
324+
'x',
325+
)? {
326+
'x' => return Ok(()),
323327
_ => continue,
324328
}
325329
} else {
@@ -329,11 +333,12 @@ fn edit_sudoers_file(
329333
"It looks like you have removed your ability to run 'sudo visudo' again.\n"
330334
)?;
331335
match ask_response(
332-
b"What now? e(x)it without saving / (e)dit again / lock me out and (S)ave: ",
333-
b"xeS",
336+
"What now? e(x)it without saving / (e)dit again / lock me out and (S)ave: ",
337+
"xeS",
338+
'x',
334339
)? {
335-
b'x' => return Ok(()),
336-
b'S' => {}
340+
'x' => return Ok(()),
341+
'S' => {}
337342
_ => continue,
338343
}
339344
}
@@ -384,42 +389,40 @@ fn sudo_visudo_is_allowed(mut sudoers: Sudoers, host_name: &Hostname) -> Option<
384389
))
385390
}
386391

387-
// Make sure that the first valid response is the "safest" choice
388-
pub(crate) fn ask_response(prompt: &[u8], valid_responses: &[u8]) -> io::Result<u8> {
392+
// This will panic if valid_responses is empty.
393+
pub(crate) fn ask_response(
394+
prompt: &str,
395+
valid_responses: &str,
396+
safe_choice: char,
397+
) -> io::Result<char> {
389398
let stdin = io::stdin();
390399
let stdout = io::stdout();
391400
let mut stderr = io::stderr();
392401

393-
let mut stdin_handle = stdin.lock();
402+
let stdin_handle = stdin.lock();
394403
let mut stdout_handle = stdout.lock();
395404

405+
let mut lines = stdin_handle.lines();
406+
396407
loop {
397-
stdout_handle.write_all(prompt)?;
408+
stdout_handle.write_all(prompt.as_bytes())?;
398409
stdout_handle.flush()?;
399410

400-
let mut input = [0u8];
401-
if let Err(err) = stdin_handle.read_exact(&mut input) {
402-
writeln!(stderr, "visudo: cannot read user input: {err}")?;
403-
return Ok(valid_responses[0]);
404-
}
405-
406-
// read the trailing newline
407-
loop {
408-
let mut skipped = [0u8];
409-
match stdin_handle.read_exact(&mut skipped) {
410-
Ok(()) if &skipped != b"\n" => continue,
411-
_ => break,
411+
match lines.next() {
412+
Some(Ok(answer))
413+
if answer
414+
.chars()
415+
.next()
416+
.is_some_and(|input| valid_responses.contains(input)) =>
417+
{
418+
return Ok(answer.chars().next().unwrap());
419+
}
420+
Some(Ok(answer)) => writeln!(stderr, "Invalid option: '{answer}'\n",)?,
421+
Some(Err(err)) => writeln!(stderr, "Invalid response: {err}\n",)?,
422+
None => {
423+
writeln!(stderr, "visudo: cannot read user input")?;
424+
return Ok(safe_choice);
412425
}
413-
}
414-
415-
if valid_responses.contains(&input[0]) {
416-
return Ok(input[0]);
417-
} else {
418-
writeln!(
419-
stderr,
420-
"Invalid option: '{}'\n",
421-
str::from_utf8(&input).unwrap_or("<INVALID>")
422-
)?;
423426
}
424427
}
425428
}

0 commit comments

Comments
 (0)