//
// Syd: rock-solid application kernel
// src/syd-cat.rs: Print out the rules of the given sandbox profile and exit
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

#![allow(non_snake_case)]

use std::{fs::File, io::BufReader};

use nix::errno::Errno;
use syd::{
    config::API_VERSION,
    err::SydResult,
    path::{XPath, XPathBuf},
    sandbox::Sandbox,
};

fn main() -> SydResult<()> {
    use lexopt::prelude::*;

    syd::set_sigpipe_dfl()?;

    // Parse CLI options.
    //
    // Note, option parsing is POSIXly correct:
    // POSIX recommends that no more options are parsed after the first
    // positional argument. The other arguments are then all treated as
    // positional arguments.
    // See: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
    let mut name = None;
    let mut optj = false;
    let mut optJ = false;
    let mut optM = Vec::new();

    let mut syd = Sandbox::new();
    let mut paths = Vec::new();
    let mut parser = lexopt::Parser::from_env();
    while let Some(arg) = parser.next()? {
        match arg {
            Short('h') => {
                help();
                return Ok(());
            }
            Short('j') => optj = true,
            Short('J') => optJ = true,
            Short('m') => {
                let cmd = parser.value().map(XPathBuf::from)?;
                if syd.locked() {
                    eprintln!("syd-cat: Failed to execute magic command `{cmd}': sandbox locked!");
                    return Err(Errno::EBUSY.into());
                } else {
                    syd.config(&cmd.to_string())?;
                }
            }
            Short('M') => optM.push(parser.value()?.parse::<String>()?),
            Short('p') => name = Some(parser.value()?.parse::<String>()?),
            Value(path) => paths.push(XPathBuf::from(path)),
            _ => return Err(arg.unexpected().into()),
        }
    }

    if optj && optJ {
        eprintln!("-j and -J are mutually exclusive!");
        return Err(Errno::EINVAL.into());
    }

    if let Some(name) = name {
        if optj || optJ {
            eprintln!("-p cannot be used with JSON output!");
            return Err(Errno::EINVAL.into());
        }
        if name == "list" {
            list();
        } else {
            dump(&name)?;
        }
        return Ok(());
    }

    for path in paths {
        let fext = if let Some(fext) = path.extension() {
            fext
        } else {
            return Err(Errno::EOPNOTSUPP.into());
        };

        let syd_ext = XPathBuf::from(format!("syd-{API_VERSION}"));
        let ips_ext = XPath::from_bytes(b"ipset");
        let net_ext = XPath::from_bytes(b"netset");

        #[allow(clippy::disallowed_methods)]
        if *fext == *syd_ext {
            syd.parse_config_file(&path)?;
        } else if *fext == *ips_ext || *fext == *net_ext {
            let file = File::open(path.as_path())?;
            syd.parse_netset(BufReader::new(file))?;
        } else {
            return Err(Errno::EOPNOTSUPP.into());
        }
    }

    for cmd in optM {
        if syd.locked() {
            eprintln!("syd-cat: Failed to execute magic command `{cmd}': sandbox locked!");
            return Err(Errno::EBUSY.into());
        } else {
            syd.config(&cmd)?;
        }
    }

    if optj {
        println!(
            "{}",
            serde_json::to_string_pretty(&syd).or(Err(Errno::EINVAL))?
        );
    } else if optJ {
        print!("{}", serde_json::to_string(&syd).or(Err(Errno::EINVAL))?);
    } else {
        print!("{syd}");
    }

    Ok(())
}

fn help() {
    println!("Usage: syd-cat [-hjJmM] [-p name] <path>...");
    println!("Tool to parse, validate and display Syd configuration.");
    println!("Given a list of paths, parses and validates configuration.");
    println!("Prints configuration to standard output on success.");
    println!("Use -j to display as JSON and -J for compact JSON output.");
    println!("Use -p <name> to display rules of the profile with the given name.");
    println!("Use -p list to get a list of profiles.");
    println!("Use -m <magic> to run a magic command at init, may be repeated.");
    println!("Use -M <magic> to run a magic command at exit, may be repeated.");
    println!("Supported configuration file extensions:");
    println!("    - ipset");
    println!("    - netset");
    println!("    - syd-{API_VERSION}");
}

fn list() {
    println!("container");
    println!("core");
    println!("debug");
    println!("enforce");
    println!("immutable");
    println!("landlock");
    println!("lib");
    println!("linux");
    println!("noipv4");
    println!("noipv6");
    println!("nomem");
    println!("nopie");
    println!("oci");
    println!("paludis");
    println!("quiet");
    println!("tty");
    println!("user");
}

fn dump(name: &str) -> SydResult<()> {
    match name {
        "container" => {
            println!("# Syd profile: Container");
            println!(
                "# Number of rules: {}",
                syd::config::PROFILE_CONTAINER.len()
            );
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_CONTAINER {
                println!("{command}");
            }
        }
        "immutable" => {
            println!("# Syd profile: Immutable Container");
            println!(
                "# Number of rules: {}",
                syd::config::PROFILE_IMMUTABLE.len()
            );
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_IMMUTABLE {
                println!("{command}");
            }
        }
        "landlock" => {
            println!("# Syd profile: LandLock");
            println!("# Number of rules: {}", syd::config::PROFILE_LANDLOCK.len());
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_LANDLOCK {
                println!("{command}");
            }
        }
        "linux" => {
            println!("# Syd profile: Linux");
            println!("# Number of rules: {}", syd::config::PROFILE_LINUX.len());
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_LINUX {
                println!("{command}");
            }
        }
        "kvm" => {
            println!("# Syd profile: KVM");
            println!("# Number of rules: {}", syd::config::PROFILE_KVM.len());
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_KVM {
                println!("{command}");
            }
        }
        "kvm_native" => {
            println!("# Syd profile: KVM-Native");
            println!(
                "# Number of rules: {}",
                syd::config::PROFILE_KVM_NATIVE.len()
            );
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_KVM_NATIVE {
                println!("{command}");
            }
        }
        "tty" => {
            println!("# Syd profile: TTY");
            println!("# Number of rules: {}", syd::config::PROFILE_TTY.len());
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_TTY {
                println!("{command}");
            }
        }
        "paludis" => {
            println!("# Syd profile: Paludis");
            println!("# Number of rules: {}", syd::config::PROFILE_PALUDIS.len());
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_PALUDIS {
                println!("{command}");
            }
        }
        "noipv4" => {
            println!("# Syd profile: NoIpv4");
            println!("# Number of rules: {}", syd::config::PROFILE_NOIPV4.len());
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_NOIPV4 {
                println!("{command}");
            }
        }
        "noipv6" => {
            println!("# Syd profile: NoIpv6");
            println!("# Number of rules: {}", syd::config::PROFILE_NOIPV6.len());
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_NOIPV6 {
                println!("{command}");
            }
        }
        "privileged" => {
            println!("# Syd profile: Privileged");
            println!(
                "# Number of rules: {}",
                syd::config::PROFILE_PRIVILEGED.len()
            );
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_PRIVILEGED {
                println!("{command}");
            }
        }
        "core" => {
            println!("# Syd profile: Allow Coredump");
            println!("# Number of rules: {}", syd::config::PROFILE_CORE.len());
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_CORE {
                println!("{command}");
            }
        }
        "debug" => {
            println!("# Syd profile: Allow Debuggers");
            println!("# Number of rules: {}", syd::config::PROFILE_DEBUG.len());
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_DEBUG {
                println!("{command}");
            }
        }
        "enforce" => {
            println!("# Syd profile: Enforce Sandboxing: set default action to Deny");
            println!("# Number of rules: {}", syd::config::PROFILE_ENFORCE.len());
            println!("# Copyright (c) 2025 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_ENFORCE {
                println!("{command}");
            }
        }
        "nomem" => {
            println!("# Syd profile: Unsafe Memory (no W^X)");
            println!("# Number of rules: {}", syd::config::PROFILE_NOMEM.len());
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_NOMEM {
                println!("{command}");
            }
        }
        "nopie" => {
            println!("# Syd profile: No PIE (Position Independent Executable)");
            println!("# Number of rules: {}", syd::config::PROFILE_NOPIE.len());
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_NOPIE {
                println!("{command}");
            }
        }
        "quiet" | "silent" => {
            println!("# Syd profile: Quiet");
            println!("# Number of rules: {}", syd::config::PROFILE_QUIET.len());
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_QUIET {
                println!("{command}");
            }
        }
        "off" => {
            println!("# Syd profile: Off");
            println!("# Number of rules: {}", syd::config::PROFILE_OFF.len());
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_OFF {
                println!("{command}");
            }
        }
        "lib" => {
            println!("# Syd profile: LibSyd");
            println!("# Number of rules: {}", syd::config::PROFILE_LIB.len());
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_LIB {
                println!("{command}");
            }
        }
        "oci" => {
            println!("# Syd profile: OCI");
            println!("# Number of rules: {}", syd::config::PROFILE_OCI.len());
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_OCI {
                println!("{command}");
            }
        }
        "trace" => {
            println!("# Syd profile: Trace");
            println!("# Number of rules: {}", syd::config::PROFILE_TRACE.len());
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_TRACE {
                println!("{command}");
            }
        }
        "user" => {
            println!("# Syd profile: User \"{name}\"");
            println!("# Number of rules: {}", syd::config::PROFILE_USER.len());
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0");
            for command in syd::config::PROFILE_USER {
                println!("{command}");
            }
        }
        _ => return Err(Errno::EINVAL.into()),
    }

    Ok(())
}
