//
// Syd: rock-solid application kernel
// src/syd-ldd.rs: Syd's secure ldd(1) wrapper
//
// Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    fs::canonicalize,
    os::unix::process::CommandExt,
    process::{Command, ExitCode},
};

use nix::{errno::Errno, unistd::ttyname};
use syd::err::SydResult;

/// Resembles the `which` command, finds a program in PATH.
fn which(command: &str, realpath: bool) -> SydResult<String> {
    let out = Command::new("which")
        .arg(command)
        .output()
        .map(|o| o.stdout)?;
    if out.is_empty() {
        return Err(Errno::ENOENT.into());
    }
    let bin = String::from_utf8_lossy(&out);
    let bin = bin.trim();
    if !realpath {
        return Ok(bin.to_string());
    }
    Ok(canonicalize(bin)?.to_string_lossy().into_owned())
}

fn main() -> SydResult<ExitCode> {
    syd::set_sigpipe_dfl()?;

    // Step 0: Determine syd path.
    let syd = if which("syd", false).is_ok() {
        "syd"
    } else {
        eprintln!("syd not found in PATH");
        return Ok(ExitCode::from(1));
    };

    // Step 1: Find the real path to ldd(1)
    let ldd = match which("ldd", true) {
        Ok(p) => p,
        Err(error) => {
            eprintln!("Failed to locate ldd: {error}");
            return Ok(ExitCode::from(2));
        }
    };

    // Step 2: Find the real path to /bin/sh
    let sh = match canonicalize("/bin/sh") {
        Ok(p) => p.to_string_lossy().to_string(),
        Err(error) => {
            eprintln!("Failed to canonicalize /bin/sh: {error}");
            return Ok(ExitCode::from(3));
        }
    };

    // Step 2: Find the path to TTYs.
    let tty_0 = ttyname(std::io::stdin())
        .map(|p| p.to_string_lossy().to_string())
        .unwrap_or("/dev/null".to_string());
    let tty_1 = ttyname(std::io::stdout())
        .map(|p| p.to_string_lossy().to_string())
        .unwrap_or("/dev/null".to_string());
    let tty_2 = ttyname(std::io::stderr())
        .map(|p| p.to_string_lossy().to_string())
        .unwrap_or("/dev/null".to_string());

    // Step 3: Gather path arguments and canonicalize to allow for read sandboxing.
    let argv: Vec<String> = std::env::args().skip(1).collect();
    let list: Vec<String> = argv
        .clone()
        .into_iter()
        .filter(|arg| !arg.starts_with('-'))
        .map(|arg| match canonicalize(&arg) {
            Ok(canonicalized_path) => {
                format!("-mallow/read+{}", canonicalized_path.to_string_lossy())
            }
            Err(_) => format!("-mallow/read+{}", arg),
        })
        .collect();

    // Step 4: Execute ldd(1) under syd.
    let _ = Command::new(syd)
        .args(list)
        .args([
            "-pimmutable",
            "-msandbox/read:on",
            "-msandbox/stat:off",
            "-msandbox/exec:on",
            "-msandbox/write:on",
            "-msandbox/net:on",
            "-msandbox/lock:on",
            "-mallow/read+/etc/ld-*.path",
            "-mallow/read+/etc/locale.alias",
            "-mallow/read+/usr/share/locale*/**/*.mo",
            "-mallow/read+/usr/share/locale*/locale.alias",
            "-mallow/read+/usr/lib*/locale*/locale-archive",
            "-mallow/read+/usr/lib*/**/gconv-modules*",
            "-mallow/read+/usr/**/LC_{ALL,COLLATE,CTYPE,IDENTIFICATION,MESSAGES}",
            "-mallow/read+/**/*.so.[0-9]*",
            "-mallow/exec+/lib/**/ld-linux*.so.[0-9]",
            "-mallow/exec+/usr/lib*/**/ld-linux*.so.[0-9]",
            "-mallow/write+/dev/null",
            "-mallow/lock/read+/",
            "-mallow/lock/write+/dev/null",
            &format!("-mallow/read+{ldd}"),
            &format!("-mallow/read+{sh}"),
            &format!("-mallow/exec+{ldd}"),
            &format!("-mallow/read+{tty_0}"),
            &format!("-mallow/write+{tty_1}"),
            &format!("-mallow/write+{tty_2}"),
            "-mlock:on",
            "--",
            "ldd",
        ])
        .args(&argv)
        .exec();
    Ok(ExitCode::from(127))
}
