use std::path::Path;
pub(crate) fn validate_secret_file(path: &Path) -> std::io::Result<String> {
use std::io::Read;
use std::os::unix::fs::{MetadataExt, OpenOptionsExt};
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
))]
const O_NOFOLLOW: i32 = 0x0100;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
const O_NOFOLLOW: i32 = 0x8000;
#[cfg(all(target_os = "linux", not(target_arch = "aarch64")))]
const O_NOFOLLOW: i32 = 0x20000;
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
const O_NOFOLLOW: i32 = 0x20000;
#[cfg(not(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
target_os = "linux",
target_os = "illumos",
target_os = "solaris",
)))]
compile_error!("O_NOFOLLOW value is unknown for this target — add it to the cfg gates above");
#[cfg(target_os = "linux")]
const ELOOP: i32 = 40;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
))]
const ELOOP: i32 = 62;
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
const ELOOP: i32 = 90;
let mut file = match std::fs::OpenOptions::new()
.read(true)
.custom_flags(O_NOFOLLOW)
.open(path)
{
Ok(f) => f,
Err(e) => {
if e.raw_os_error() == Some(ELOOP) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("{}: must not be a symlink", path.display()),
));
}
return Err(e);
}
};
let meta = file.metadata()?;
if !meta.is_file() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("{}: must be a regular file", path.display()),
));
}
let mode = meta.mode() & 0o777;
if mode & 0o077 != 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
format!(
"{}: insecure permissions {:03o} (must be 0600 or stricter)",
path.display(),
mode
),
));
}
let my_uid = crate::peer_cred::observer_uid();
let file_uid = meta.uid();
if file_uid != my_uid {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
format!(
"{}: owned by uid {file_uid}, expected uid {my_uid}",
path.display()
),
));
}
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
pub(super) fn validate_recovery_file(path: &Path) -> std::io::Result<String> {
let content = validate_secret_file(path)?;
Ok(content.trim().to_string())
}
#[cfg(feature = "secure-udp")]
pub(super) fn read_secret_file(path: &Path) -> std::io::Result<String> {
validate_secret_file(path)
}
pub fn parse_exec_cmd(cmd: &str) -> std::io::Result<(String, Vec<String>)> {
let mut parts: Vec<&str> = cmd.split_whitespace().collect();
if parts.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"recovery command must not be empty",
));
}
let program = parts.remove(0).to_string();
let args: Vec<String> = parts.into_iter().map(|s| s.to_string()).collect();
Ok((program, args))
}