@@ -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,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