]>
Commit | Line | Data |
---|---|---|
c9b296f1 WB |
1 | //! Helpers for terminal interaction |
2 | ||
3 | use std::io::{Read, Write}; | |
48b85e8e | 4 | use std::mem::MaybeUninit; |
c9b296f1 WB |
5 | use std::os::unix::io::AsRawFd; |
6 | ||
7 | use failure::*; | |
8 | ||
9ea4bce4 | 9 | use proxmox::try_block; |
c9b296f1 WB |
10 | |
11 | /// Returns whether the current stdin is a tty . | |
12 | pub fn stdin_isatty() -> bool { | |
13 | unsafe { libc::isatty(std::io::stdin().as_raw_fd()) == 1 } | |
14 | } | |
15 | ||
16 | /// Read a password from stdin, masking the echoed output with asterisks and writing a query first. | |
17 | pub fn read_password(query: &str) -> Result<Vec<u8>, Error> { | |
18 | let input = std::io::stdin(); | |
19 | if unsafe { libc::isatty(input.as_raw_fd()) } != 1 { | |
20 | let mut out = String::new(); | |
21 | input.read_line(&mut out)?; | |
22 | return Ok(out.into_bytes()); | |
23 | } | |
24 | ||
25 | let mut out = std::io::stdout(); | |
26 | let _ignore_error = out.write_all(query.as_bytes()); | |
27 | let _ignore_error = out.flush(); | |
28 | ||
29 | let infd = input.as_raw_fd(); | |
48b85e8e WB |
30 | let mut termios = MaybeUninit::<libc::termios>::uninit(); |
31 | if unsafe { libc::tcgetattr(infd, &mut *termios.as_mut_ptr()) } != 0 { | |
c9b296f1 WB |
32 | bail!("tcgetattr() failed"); |
33 | } | |
48b85e8e | 34 | let mut termios = unsafe { termios.assume_init() }; |
62ee2eb4 | 35 | let old_termios = termios; // termios is a 'Copy' type |
c9b296f1 WB |
36 | unsafe { |
37 | libc::cfmakeraw(&mut termios); | |
38 | } | |
39 | if unsafe { libc::tcsetattr(infd, libc::TCSANOW, &termios) } != 0 { | |
40 | bail!("tcsetattr() failed"); | |
41 | } | |
42 | ||
43 | let mut password = Vec::<u8>::new(); | |
44 | let mut asterisks = true; | |
45 | ||
46 | let ok: Result<(), Error> = try_block!({ | |
47 | for byte in input.bytes() { | |
48 | let byte = byte?; | |
49 | match byte { | |
50 | 3 => bail!("cancelled"), // ^C | |
48b85e8e WB |
51 | 4 => break, // ^D / EOF |
52 | 9 => asterisks = false, // tab disables echo | |
53 | 0xA | 0xD => { | |
54 | // newline, we're done | |
c9b296f1 WB |
55 | let _ignore_error = out.write_all("\r\n".as_bytes()); |
56 | let _ignore_error = out.flush(); | |
57 | break; | |
58 | } | |
48b85e8e WB |
59 | 0x7F => { |
60 | // backspace | |
62ee2eb4 | 61 | if !password.is_empty() { |
c9b296f1 WB |
62 | password.pop(); |
63 | if asterisks { | |
64 | let _ignore_error = out.write_all("\x08 \x08".as_bytes()); | |
65 | let _ignore_error = out.flush(); | |
66 | } | |
67 | } | |
68 | } | |
69 | other => { | |
70 | password.push(other); | |
71 | if asterisks { | |
62ee2eb4 | 72 | let _ignore_error = out.write_all(b"*"); |
c9b296f1 WB |
73 | let _ignore_error = out.flush(); |
74 | } | |
75 | } | |
76 | } | |
77 | } | |
78 | Ok(()) | |
79 | }); | |
80 | if unsafe { libc::tcsetattr(infd, libc::TCSANOW, &old_termios) } != 0 { | |
81 | // not fatal... | |
82 | eprintln!("failed to reset terminal attributes!"); | |
83 | } | |
84 | match ok { | |
85 | Ok(_) => Ok(password), | |
86 | Err(e) => Err(e), | |
87 | } | |
88 | } | |
cbe01dc5 OB |
89 | |
90 | pub fn read_and_verify_password(prompt: &str) -> Result<Vec<u8>, Error> { | |
91 | ||
92 | let password = String::from_utf8(crate::tools::tty::read_password(prompt)?)?; | |
93 | let verify_password = String::from_utf8(crate::tools::tty::read_password("Verify Password: ")?)?; | |
94 | ||
95 | if password != verify_password { | |
96 | bail!("Passwords do not match!"); | |
97 | } | |
98 | ||
99 | if password.len() < 5 { | |
100 | bail!("Password too short!"); | |
101 | } | |
102 | ||
103 | Ok(password.into_bytes()) | |
104 | } |