From 4d21db36fe904fa7ba7752bb0924e80fbe3a1901 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Fri, 31 Jan 2020 07:58:48 +0100 Subject: [PATCH] src/sys/linux/tty.rs: add tty helpers --- proxmox/src/sys/linux.rs | 1 + proxmox/src/sys/linux/tty.rs | 132 +++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 proxmox/src/sys/linux/tty.rs diff --git a/proxmox/src/sys/linux.rs b/proxmox/src/sys/linux.rs index 02a54f2f..ea93254f 100644 --- a/proxmox/src/sys/linux.rs +++ b/proxmox/src/sys/linux.rs @@ -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, Error> { diff --git a/proxmox/src/sys/linux/tty.rs b/proxmox/src/sys/linux/tty.rs new file mode 100644 index 00000000..5a5b1823 --- /dev/null +++ b/proxmox/src/sys/linux/tty.rs @@ -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, 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::::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::::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, 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()) +}