1use std::{ffi::CStr, io, mem, str::Utf8Error};
10
11use bitflags::bitflags;
12use thiserror::Error;
13use zeroize::Zeroizing;
14
15const PASSWORD_LEN: usize = 256;
16
17bitflags! {
18 pub struct RppFlags: i32 {
20 const ECHO_OFF = 0x00;
22 const ECHO_ON = 0x01;
24 const REQUIRE_TTY = 0x02;
26 const FORCELOWER = 0x04;
28 const FORCEUPPER = 0x08;
30 const SEVENBIT = 0x10;
32 const STDIN = 0x20;
34 }
35}
36
37impl Default for RppFlags {
38 fn default() -> Self {
39 Self::ECHO_OFF
40 }
41}
42
43#[derive(Error, Debug)]
44pub enum Error {
45 #[error(transparent)]
46 IoError(#[from] io::Error),
47 #[error(transparent)]
48 Utf8Error(#[from] Utf8Error),
49}
50
51pub fn readpassphrase(prompt: &CStr, flags: RppFlags) -> Result<String, Error> {
55 readpassphrase_buf(prompt, vec![0u8; PASSWORD_LEN], flags)
56}
57
58pub fn readpassphrase_buf(prompt: &CStr, buf: Vec<u8>, flags: RppFlags) -> Result<String, Error> {
62 let mut buf = Zeroizing::new(buf);
63 unsafe {
64 let res = ffi::readpassphrase(
65 prompt.as_ptr(),
66 buf.as_mut_ptr().cast(),
67 buf.len(),
68 flags.bits(),
69 );
70 if res.is_null() {
71 return Err(io::Error::last_os_error().into());
72 }
73 }
74 let nul_pos = buf
75 .iter()
76 .position(|&b| b == 0)
77 .ok_or(io::Error::from(io::ErrorKind::InvalidData))?;
78 buf.truncate(nul_pos);
79 let _ = str::from_utf8(&buf)?;
80 Ok(unsafe { String::from_utf8_unchecked(mem::take(&mut buf)) })
81}
82
83mod ffi {
84 use std::ffi::{c_char, c_int};
85
86 unsafe extern "C" {
87 pub(crate) unsafe fn readpassphrase(
88 prompt: *const c_char,
89 buf: *mut c_char,
90 bufsiz: usize,
91 flags: c_int,
92 ) -> *mut c_char;
93 }
94}