use anyhow::{Result, bail};
use std::io::Read;
const PASSPHRASE_ENV: &str = "PASSBOX_PASSPHRASE";
pub(crate) fn without_credentials(
command: &mut std::process::Command,
) -> &mut std::process::Command {
command
.env_remove("PASSBOX_TOKEN")
.env_remove(PASSPHRASE_ENV)
}
struct Held {
token: Option<zeroize::Zeroizing<String>>,
passphrase: Option<zeroize::Zeroizing<String>>,
}
static HELD: std::sync::OnceLock<Held> = std::sync::OnceLock::new();
pub(crate) fn take_credentials() {
let _ = HELD.set(Held {
token: take_env("PASSBOX_TOKEN"),
passphrase: take_env(PASSPHRASE_ENV),
});
}
fn take_env(name: &str) -> Option<zeroize::Zeroizing<String>> {
let value = zeroize::Zeroizing::new(std::env::var(name).ok()?);
wipe_env_value(name);
unsafe { std::env::remove_var(name) };
Some(value)
}
fn wipe_env_value(name: &str) {
use zeroize::Zeroize;
#[cfg(target_os = "macos")]
let table = unsafe { libc::_NSGetEnviron() };
#[cfg(target_os = "macos")]
let mut entry = unsafe { *table };
#[cfg(not(target_os = "macos"))]
let mut entry = unsafe { environ };
let prefix = format!("{name}=");
loop {
let line = unsafe { *entry };
if line.is_null() {
break;
}
let bytes = unsafe { std::ffi::CStr::from_ptr(line) }.to_bytes();
if let Some(value) = bytes.strip_prefix(prefix.as_bytes()) {
let start = unsafe { line.add(prefix.len()) }.cast::<u8>();
unsafe { std::slice::from_raw_parts_mut(start, value.len()) }.zeroize();
}
entry = unsafe { entry.add(1) };
}
}
#[cfg(not(target_os = "macos"))]
unsafe extern "C" {
static environ: *mut *mut libc::c_char;
}
static STDIN_TOKEN: std::sync::OnceLock<zeroize::Zeroizing<String>> = std::sync::OnceLock::new();
pub(crate) fn held_token() -> Option<&'static str> {
if let Some(token) = STDIN_TOKEN.get() {
return Some(token.as_str());
}
HELD.get()?.token.as_deref().map(String::as_str)
}
pub(crate) fn take_stdin_token() -> Result<()> {
use std::os::unix::io::FromRawFd;
let mut stdin = std::mem::ManuallyDrop::new(unsafe { std::fs::File::from_raw_fd(0) });
let mut token = zeroize::Zeroizing::new(String::new());
let mut byte = [0u8; 1];
while stdin.read(&mut byte)? == 1 && byte[0] != b'\n' {
token.push(char::from(byte[0]));
}
let token = zeroize::Zeroizing::new(token.trim().to_string());
if token.is_empty() {
bail!("--token-stdin read no token");
}
let _ = STDIN_TOKEN.set(token);
Ok(())
}
pub(crate) fn held_passphrase() -> Option<&'static str> {
HELD.get()?.passphrase.as_deref().map(String::as_str)
}