#[cfg(unix)]
use std::io::Write;
use super::Uz;
#[cfg(unix)]
use super::messages as m;
use crate::compat::host::Host;
#[cfg(unix)]
use crate::compat::host::TtyMode;
#[cfg(unix)]
pub(crate) const IZ_PWLEN: usize = 80;
pub(crate) struct Password(Vec<u8>);
impl Password {
pub(crate) fn new(bytes: &[u8]) -> Password {
Password(bytes.to_vec())
}
pub(crate) fn from_vec(bytes: Vec<u8>) -> Password {
Password(bytes)
}
pub(crate) fn key(&self) -> &[u8] {
match self.0.iter().position(|&b| b == 0) {
Some(n) => &self.0[..n],
None => &self.0,
}
}
pub(crate) fn is_empty_answer(&self) -> bool {
self.0.first().is_none_or(|&b| b == 0)
}
fn clear(&mut self) {
wipe(&mut self.0);
self.0.clear();
}
}
pub(crate) fn wipe(bytes: &mut [u8]) {
for b in bytes.iter_mut() {
unsafe { std::ptr::write_volatile(b, 0) };
}
std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);
}
impl Drop for Password {
fn drop(&mut self) {
self.clear();
}
}
impl std::fmt::Debug for Password {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Password(..)")
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum PwError {
NoTerminal,
#[cfg(unix)]
HungUp,
}
#[cfg(unix)]
enum Got {
Byte(u8),
Nothing,
Gone,
}
#[cfg(unix)]
fn read_one(fd: libc::c_int) -> Got {
let mut b = 0u8;
loop {
let n = unsafe { libc::read(fd, (&mut b as *mut u8).cast(), 1) };
if n == 1 {
return Got::Byte(b);
}
if n < 0 {
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
continue;
}
return Got::Gone;
}
let mut pfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
let r = unsafe { libc::poll(&mut pfd, 1, 0) };
let hung = libc::POLLHUP | libc::POLLERR | libc::POLLNVAL;
return if r > 0 && pfd.revents & hung != 0 {
Got::Gone
} else {
Got::Nothing
};
}
}
pub(crate) fn getp(uz: &mut Uz, prompt: &[u8]) -> Result<Password, PwError> {
if matches!(uz.host, Host::Sandbox(_)) {
return Err(PwError::NoTerminal);
}
#[cfg(unix)]
{
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
let f = unsafe { libc::open(m::PATH_TTY.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
if f == -1 {
return Err(PwError::NoTerminal);
}
let f = unsafe { OwnedFd::from_raw_fd(f) };
getp_on(&mut *uz.err, f.as_raw_fd(), prompt, IZ_PWLEN + 1)
}
#[cfg(not(unix))]
{
let _ = prompt;
Err(PwError::NoTerminal)
}
}
#[cfg(unix)]
pub(crate) fn getp_on(
err: &mut dyn Write,
f: libc::c_int,
prompt: &[u8],
n: usize,
) -> Result<Password, PwError> {
let mut p = Password(Vec::with_capacity(n));
let mut w: &[u8] = b"";
loop {
let _ = err.write_all(w); let _ = err.write_all(prompt); let _ = err.flush();
p.clear();
{
let _echo = TtyMode::set(f, |t| t.c_lflag &= !libc::ECHO); let mut c: Option<u8> = None;
loop {
match read_one(f) {
Got::Byte(b) => c = Some(b),
Got::Nothing => {
c.get_or_insert(b'\n');
}
Got::Gone => return Err(PwError::HungUp),
}
let ch = c.unwrap_or(b'\n');
if p.0.len() < n {
p.0.push(ch);
}
if ch == b'\n' {
break;
}
}
} let _ = err.write_all(m::GETP_NEWLINE.as_bytes());
let _ = err.flush();
w = m::LINE_TOO_LONG.as_bytes();
if p.0.last() == Some(&b'\n') {
break;
}
}
if let Some(last) = p.0.last_mut() {
*last = 0;
}
p.0.pop();
Ok(p)
}
pub(crate) fn zgetch(uz: &mut Uz) -> Option<u8> {
#[cfg(unix)]
if matches!(uz.host, Host::Real) {
let _cbreak = TtyMode::set(0, |t| {
t.c_cc[libc::VMIN] = 1; t.c_cc[libc::VTIME] = 0; t.c_lflag &= !(libc::ICANON | libc::ECHO); });
return match read_one(0) {
Got::Byte(b) => Some(b),
Got::Nothing | Got::Gone => None,
};
}
let input = uz.stdin();
let c = *input.fill_buf().ok()?.first()?;
input.consume(1);
Some(c)
}
pub(crate) fn screensize(host: &Host) -> (i32, i32) {
#[cfg(unix)]
if matches!(host, Host::Real) {
let mut ws: libc::winsize = unsafe { std::mem::zeroed() };
if unsafe { libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) } == 0 {
let rows = if ws.ws_row > 0 {
i32::from(ws.ws_row)
} else {
24
};
let cols = if ws.ws_col > 0 {
i32::from(ws.ws_col)
} else {
80
};
return (rows, cols);
}
}
let _ = host;
(24, 80)
}
#[cfg(all(test, unix))]
mod tests;