1use std::{
10 ffi::{CStr, FromBytesUntilNulError}, fmt::Display, io, mem, str::Utf8Error
11};
12
13use bitflags::bitflags;
14#[cfg(feature = "zeroize")]
15use zeroize::Zeroizing;
16
17const PASSWORD_LEN: usize = 256;
18
19bitflags! {
20 pub struct RppFlags: i32 {
22 const ECHO_OFF = 0x00;
24 const ECHO_ON = 0x01;
26 const REQUIRE_TTY = 0x02;
28 const FORCELOWER = 0x04;
30 const FORCEUPPER = 0x08;
32 const SEVENBIT = 0x10;
34 const STDIN = 0x20;
36 }
37}
38
39impl Default for RppFlags {
40 fn default() -> Self {
41 Self::ECHO_OFF
42 }
43}
44
45#[derive(Debug)]
46pub enum Error {
47 Io(io::Error),
48 Utf8(Utf8Error),
49 CStr(FromBytesUntilNulError),
50}
51
52pub fn readpassphrase(prompt: &CStr, flags: RppFlags) -> Result<String, Error> {
56 readpassphrase_buf(prompt, vec![0u8; PASSWORD_LEN], flags)
57}
58
59pub fn readpassphrase_buf(
63 prompt: &CStr,
64 #[allow(unused_mut)] mut buf: Vec<u8>,
65 flags: RppFlags,
66) -> Result<String, Error> {
67 #[cfg(feature = "zeroize")]
68 let mut buf = Zeroizing::new(buf);
69 unsafe {
70 let res = ffi::readpassphrase(
71 prompt.as_ptr(),
72 buf.as_mut_ptr().cast(),
73 buf.len(),
74 flags.bits(),
75 );
76 if res.is_null() {
77 return Err(io::Error::last_os_error().into());
78 }
79 }
80 let nul_pos = buf
81 .iter()
82 .position(|&b| b == 0)
83 .ok_or(io::Error::from(io::ErrorKind::InvalidData))?;
84 buf.truncate(nul_pos);
85 let _ = str::from_utf8(&buf)?;
86 Ok(unsafe { String::from_utf8_unchecked(mem::take(&mut buf)) })
87}
88
89pub fn readpassphrase_inplace<'a>(
93 prompt: &CStr,
94 buf: &'a mut [u8],
95 flags: RppFlags,
96) -> Result<&'a str, Error> {
97 unsafe {
98 let res = ffi::readpassphrase(
99 prompt.as_ptr(),
100 buf.as_mut_ptr().cast(),
101 buf.len(),
102 flags.bits(),
103 );
104 if res.is_null() {
105 return Err(io::Error::last_os_error().into());
106 }
107 }
108 let res = CStr::from_bytes_until_nul(buf)?;
109 Ok(res.to_str()?)
110}
111
112impl From<io::Error> for Error {
113 fn from(value: io::Error) -> Self {
114 Error::Io(value)
115 }
116}
117
118impl From<Utf8Error> for Error {
119 fn from(value: Utf8Error) -> Self {
120 Error::Utf8(value)
121 }
122}
123
124impl From<FromBytesUntilNulError> for Error {
125 fn from(value: FromBytesUntilNulError) -> Self {
126 Error::CStr(value)
127 }
128}
129
130impl Display for Error {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 match self {
133 Error::Io(e) => e.fmt(f),
134 Error::Utf8(e) => e.fmt(f),
135 Error::CStr(e) => e.fmt(f),
136 }
137 }
138}
139
140mod ffi {
141 use std::ffi::{c_char, c_int};
142
143 unsafe extern "C" {
144 pub(crate) unsafe fn readpassphrase(
145 prompt: *const c_char,
146 buf: *mut c_char,
147 bufsiz: usize,
148 flags: c_int,
149 ) -> *mut c_char;
150 }
151}