readpassphrase_3/
lib.rs

1// Copyright 2025 Steven Dee.
2//
3// This project is dual licensed under the MIT and Apache 2.0 licenses. See the
4// LICENSE file in the project root for details.
5//
6// The readpassphrase source and header are copyright 2000-2002, 2007, 2010 Todd
7// C. Miller.
8
9use 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    /// Flags for controlling readpassphrase
19    pub struct RppFlags: i32 {
20        /// Furn off echo (default)
21        const ECHO_OFF    = 0x00;
22        /// Leave echo on
23        const ECHO_ON     = 0x01;
24        /// Fail if there is no tty
25        const REQUIRE_TTY = 0x02;
26        /// Force input to lower case
27        const FORCELOWER  = 0x04;
28        /// Force input to upper case
29        const FORCEUPPER  = 0x08;
30        /// Strip the high bit from input
31        const SEVENBIT    = 0x10;
32        /// Read from stdin, not /dev/tty
33        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
51/// Reads a passphrase using `readpassphrase(3)`, returning it as a `String`.
52/// Internally uses a buffer of `PASSWORD_LEN` bytes, allowing for passwords
53/// up to `PASSWORD_LEN - 1` characters (including the null terminator.)
54pub fn readpassphrase(prompt: &CStr, flags: RppFlags) -> Result<String, Error> {
55    readpassphrase_buf(prompt, vec![0u8; PASSWORD_LEN], flags)
56}
57
58/// Reads a passphrase using `readpassphrase(3)` into the passed buffer.
59/// Returns a `String` consisting of the same memory from the buffer, or
60/// else zeroes the buffer on error.
61pub 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}