mod ancestry;
mod own_binary;
use std::io::{self, BufRead as _, IsTerminal as _};
use anyhow::{Result, bail};
#[cfg(debug_assertions)]
const ASSUME_TTY: &str = "MOAT_ASSUME_TTY";
fn assumed() -> bool {
#[cfg(debug_assertions)]
if std::env::var_os(ASSUME_TTY).is_some_and(|v| v == "1") {
static NOTED: std::sync::Once = std::sync::Once::new();
NOTED.call_once(|| eprintln!("moat: {ASSUME_TTY}=1 honoured (debug build only)"));
return true;
}
false
}
pub fn interactive() -> bool {
assumed() || std::io::stdin().is_terminal()
}
pub fn person() -> bool {
person_for("openmoat")
}
pub fn person_for(command: &str) -> bool {
interactive() && refuse_agent(command).is_ok()
}
fn checks_binary(command: &str) -> bool {
command != "openmoat init"
}
pub fn refuse_agent(command: &str) -> Result<()> {
if assumed() {
return Ok(());
}
if checks_binary(command) {
own_binary::refuse_copy(command)?;
}
match ancestry::agent_ancestor() {
Ok(None) => Ok(()),
Ok(Some(agent)) => bail!(
"`{command}` must be run by a person, not by an agent: it was started from \
`{agent}`. Run it in a terminal of your own"
),
Err(error) => bail!(
"`{command}` must be run by a person, and the programs that started it cannot \
be checked ({error}). Run it in a terminal of your own"
),
}
}
pub fn read_answer() -> io::Result<String> {
let mut line = String::new();
if assumed() || cfg!(windows) {
io::stdin().lock().read_line(&mut line)?;
return Ok(line);
}
match std::fs::File::open("/dev/tty") {
Ok(tty) => {
io::BufReader::new(tty).read_line(&mut line)?;
}
Err(error) => eprintln!("moat: no controlling terminal to read the answer from: {error}"),
}
Ok(line)
}
#[cfg(test)]
mod tests {
use super::checks_binary;
#[test]
fn only_init_runs_from_a_binary_the_lock_does_not_record() {
assert!(!checks_binary("openmoat init"));
for command in [
"openmoat",
"openmoat allow",
"openmoat trust",
"openmoat uninstall",
] {
assert!(checks_binary(command), "{command}");
}
}
}