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