]> git.proxmox.com Git - proxmox.git/commitdiff
src/sys/linux/tty.rs: add tty helpers
authorDietmar Maurer <dietmar@proxmox.com>
Fri, 31 Jan 2020 06:58:48 +0000 (07:58 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Fri, 31 Jan 2020 06:58:48 +0000 (07:58 +0100)
proxmox/src/sys/linux.rs
proxmox/src/sys/linux/tty.rs [new file with mode: 0644]

index 02a54f2fc0268d06db136185e1bd6e3245c466d6..ea93254f04a2d054c315d1a8e52997aaf3b6647d 100644 (file)
@@ -5,6 +5,7 @@ use failure::*;
 pub mod magic;
 pub mod pid;
 pub mod procfs;
+pub mod tty;
 
 /// Get pseudo random data (/dev/urandom)
 pub fn random_data(size: usize) -> Result<Vec<u8>, Error> {
diff --git a/proxmox/src/sys/linux/tty.rs b/proxmox/src/sys/linux/tty.rs
new file mode 100644 (file)
index 0000000..5a5b182
--- /dev/null
@@ -0,0 +1,132 @@
+use std::io::{Read, Write};
+use std::mem::MaybeUninit;
+use std::os::unix::io::AsRawFd;
+
+use failure::*;
+
+use crate::try_block;
+
+/// Get the current size of the terminal (for stdout).
+/// # Safety
+///
+/// uses unsafe call to tty_ioctl, see man tty_ioctl(2).
+pub fn stdout_terminal_size() -> (usize, usize) {
+    let mut winsize = libc::winsize {
+        ws_row: 0,
+        ws_col: 0,
+        ws_xpixel: 0,
+        ws_ypixel: 0,
+    };
+    unsafe { libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut winsize) };
+    (winsize.ws_row as usize, winsize.ws_col as usize)
+}
+
+/// Returns whether the current stdout is a tty .
+/// # Safety
+///
+/// uses unsafe call to libc::isatty
+pub fn stdout_isatty() -> bool {
+    unsafe { libc::isatty(std::io::stdin().as_raw_fd()) == 1 }
+}
+
+/// Returns whether the current stdin is a tty .
+/// # Safety
+///
+/// uses unsafe call to libc::isatty
+pub fn stdin_isatty() -> bool {
+    unsafe { libc::isatty(std::io::stdin().as_raw_fd()) == 1 }
+}
+
+/// Read a password from stdin.
+///
+/// Masking the echoed output with asterisks and writing a query
+/// first.
+pub fn read_password(query: &str) -> Result<Vec<u8>, Error> {
+    let input = std::io::stdin();
+    if unsafe { libc::isatty(input.as_raw_fd()) } != 1 {
+        let mut out = String::new();
+        input.read_line(&mut out)?;
+        return Ok(out.into_bytes());
+    }
+
+    let mut out = std::io::stdout();
+    let _ignore_error = out.write_all(query.as_bytes());
+    let _ignore_error = out.flush();
+
+    let infd = input.as_raw_fd();
+    let mut termios = MaybeUninit::<libc::termios>::uninit();
+    if unsafe { libc::tcgetattr(infd, &mut *termios.as_mut_ptr()) } != 0 {
+        bail!("tcgetattr() failed");
+    }
+    let mut termios = unsafe { termios.assume_init() };
+    let old_termios = termios; // termios is a 'Copy' type
+    unsafe {
+        libc::cfmakeraw(&mut termios);
+    }
+    if unsafe { libc::tcsetattr(infd, libc::TCSANOW, &termios) } != 0 {
+        bail!("tcsetattr() failed");
+    }
+
+    let mut password = Vec::<u8>::new();
+    let mut asterisks = true;
+
+    let ok: Result<(), Error> = try_block!({
+        for byte in input.bytes() {
+            let byte = byte?;
+            match byte {
+                3 => bail!("cancelled"), // ^C
+                4 => break,              // ^D / EOF
+                9 => asterisks = false,  // tab disables echo
+                0xA | 0xD => {
+                    // newline, we're done
+                    let _ignore_error = out.write_all("\r\n".as_bytes());
+                    let _ignore_error = out.flush();
+                    break;
+                }
+                0x7F => {
+                    // backspace
+                    if !password.is_empty() {
+                        password.pop();
+                        if asterisks {
+                            let _ignore_error = out.write_all("\x08 \x08".as_bytes());
+                            let _ignore_error = out.flush();
+                        }
+                    }
+                }
+                other => {
+                    password.push(other);
+                    if asterisks {
+                        let _ignore_error = out.write_all(b"*");
+                        let _ignore_error = out.flush();
+                    }
+                }
+            }
+        }
+        Ok(())
+    });
+    if unsafe { libc::tcsetattr(infd, libc::TCSANOW, &old_termios) } != 0 {
+        // not fatal...
+        eprintln!("failed to reset terminal attributes!");
+    }
+    match ok {
+        Ok(_) => Ok(password),
+        Err(e) => Err(e),
+    }
+}
+
+/// Read a password from stdin, then read again to verify it.
+pub fn read_and_verify_password(prompt: &str) -> Result<Vec<u8>, Error> {
+
+    let password = String::from_utf8(read_password(prompt)?)?;
+    let verify_password = String::from_utf8(read_password("Verify Password: ")?)?;
+
+    if password != verify_password {
+        bail!("Passwords do not match!");
+    }
+
+    if password.len() < 5 {
+        bail!("Password too short!");
+    }
+
+    Ok(password.into_bytes())
+}