@@ -6,7 +6,7 @@ mod help;
66use 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,8 @@ 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 ( "What now? e(x)it without saving / (e)dit again: " , "xe" ) ? {
322+ 'x' => return Ok ( ( ) ) ,
323323 _ => continue ,
324324 }
325325 } else {
@@ -329,11 +329,11 @@ fn edit_sudoers_file(
329329 "It looks like you have removed your ability to run 'sudo visudo' again.\n "
330330 ) ?;
331331 match ask_response (
332- b "What now? e(x)it without saving / (e)dit again / lock me out and (S)ave: ",
333- b "xeS",
332+ "What now? e(x)it without saving / (e)dit again / lock me out and (S)ave: " ,
333+ "xeS" ,
334334 ) ? {
335- b 'x' => return Ok ( ( ) ) ,
336- b 'S' => { }
335+ 'x' => return Ok ( ( ) ) ,
336+ 'S' => { }
337337 _ => continue ,
338338 }
339339 }
@@ -385,41 +385,41 @@ fn sudo_visudo_is_allowed(mut sudoers: Sudoers, host_name: &Hostname) -> Option<
385385}
386386
387387// 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 > {
388+ // This will panic if valid_responses is empty.
389+ pub ( crate ) fn ask_response ( prompt : & str , valid_responses : & str ) -> io:: Result < char > {
389390 let stdin = io:: stdin ( ) ;
390391 let stdout = io:: stdout ( ) ;
391392 let mut stderr = io:: stderr ( ) ;
392393
393- let mut stdin_handle = stdin. lock ( ) ;
394+ let stdin_handle = stdin. lock ( ) ;
394395 let mut stdout_handle = stdout. lock ( ) ;
395396
397+ let mut lines = stdin_handle. lines ( ) ;
398+
396399 loop {
397- stdout_handle. write_all ( prompt) ?;
400+ stdout_handle. write_all ( prompt. as_bytes ( ) ) ?;
398401 stdout_handle. flush ( ) ?;
399402
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 ,
403+ match lines. next ( ) {
404+ Some ( Ok ( answer) )
405+ if answer
406+ . chars ( )
407+ . next ( )
408+ . is_some_and ( |input| valid_responses. contains ( input) ) =>
409+ {
410+ return Ok ( answer. chars ( ) . next ( ) . unwrap ( ) ) ;
411+ }
412+ Some ( Ok ( answer) ) => writeln ! ( stderr, "Invalid option: '{answer}'\n " , ) ?,
413+ Some ( Err ( err) ) => writeln ! ( stderr, "Invalid response: {err}\n " , ) ?,
414+ None => {
415+ let safe_choice = valid_responses
416+ . chars ( )
417+ . next ( )
418+ . expect ( "at least one response" ) ;
419+
420+ writeln ! ( stderr, "visudo: cannot read user input" ) ?;
421+ return Ok ( safe_choice) ;
412422 }
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- ) ?;
423423 }
424424 }
425425}
0 commit comments