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

#![allow(non_snake_case)]

use std::{ffi::OsString, fs::File, io::BufReader, path::Path};

use getargs::{Opt, Options};
use nix::{
    errno::Errno,
    unistd::{Gid, Uid},
};
use syd::{config::API_VERSION, err::SydResult, sandbox::Sandbox};

// Set global allocator to mimalloc.
#[global_allocator]
static GLOBAL: mimalloc2_rust::GlobalMiMalloc = mimalloc2_rust::GlobalMiMalloc;

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

    // Configure mimalloc.
    syd::configure_mimalloc();

    let args = std::env::args().skip(1).collect::<Vec<_>>();
    let args: &'static Vec<String> = Box::leak(Box::new(args));
    let mut opts = Options::new(args.iter().map(String::as_str));
    let mut name = None;
    let mut optj = false;
    let mut optJ = false;
    let mut optM = Vec::new();

    let mut syd = Sandbox::new();
    while let Some(opt) = opts.next_opt()? {
        match opt {
            Opt::Short('h') => {
                help();
                return Ok(());
            }
            Opt::Short('j') => {
                optj = true;
            }
            Opt::Short('J') => {
                optJ = true;
            }
            Opt::Short('m') => match opts.value() {
                Ok(cmd) => {
                    if syd.locked() {
                        eprintln!("Failed to execute magic command `{cmd}': sandbox locked!");
                        return Err(Errno::EBUSY.into());
                    } else {
                        syd.config(cmd)?;
                    }
                }
                Err(_) => {
                    eprintln!("-m requires an argument!");
                    return Err(Errno::EINVAL.into());
                }
            },
            Opt::Short('M') => match opts.value() {
                Ok(cmd) => optM.push(cmd),
                Err(_) => {
                    eprintln!("-M requires an argument!");
                    return Err(Errno::EINVAL.into());
                }
            },
            Opt::Short('p') => match opts.value() {
                Ok(val) => name = Some(val),
                Err(_) => {
                    eprintln!("-p requires an argument!");
                    return Err(Errno::EINVAL.into());
                }
            },
            Opt::Short(opt) => {
                eprintln!("Unknown option: -{opt}!");
                help();
                return Err(Errno::EINVAL.into());
            }
            Opt::Long(opt) => {
                eprintln!("Unknown option: --{opt}!");
                help();
                return Err(Errno::EINVAL.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 opts.positionals() {
        let fext = if let Some(fext) = Path::new(path).extension() {
            fext
        } else {
            return Err(Errno::EOPNOTSUPP.into());
        };
        let syd_ext = OsString::from(format!("syd-{API_VERSION}"));
        let ips_ext = Path::new("ipset");
        let net_ext = Path::new("netset");
        if fext == syd_ext {
            syd.parse_config_file(path)?;
        } else if fext == ips_ext || fext == net_ext {
            let file = File::open(path)?;
            syd.parse_netset(BufReader::new(file))?;
        } else {
            return Err(Errno::EOPNOTSUPP.into());
        }
    }

    for cmd in optM {
        if syd.locked() {
            eprintln!("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!("immutable");
    println!("landlock");
    println!("lib");
    println!("linux");
    println!("noexe");
    println!("noipv4");
    println!("noipv6");
    println!("nomem");
    println!("nopie");
    println!("oci");
    println!("paludis");
    println!("pandora");
    println!("silent");
    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-or-later");
            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-or-later");
            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-or-later");
            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-or-later");
            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-or-later");
            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-or-later");
            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-or-later");
            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-or-later");
            for command in syd::config::PROFILE_PALUDIS {
                println!("{command}");
            }
        }
        "pandora" => {
            println!("# Syd profile: Pand☮ra");
            println!("# Number of rules: {}", syd::config::PROFILE_PANDORA.len());
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0-or-later");
            for command in syd::config::PROFILE_PANDORA {
                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-or-later");
            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-or-later");
            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-or-later");
            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-or-later");
            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-or-later");
            for command in syd::config::PROFILE_DEBUG {
                println!("{command}");
            }
        }
        "noexe" => {
            println!("# Syd profile: Unsafe Exec");
            println!("# Number of rules: {}", syd::config::PROFILE_NOEXE.len());
            println!("# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0-or-later");
            for command in syd::config::PROFILE_NOEXE {
                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-or-later");
            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-or-later");
            for command in syd::config::PROFILE_NOPIE {
                println!("{command}");
            }
        }
        "silent" => {
            println!("# Syd profile: Silent");
            println!("# Number of rules: {}", syd::config::PROFILE_SILENT.len());
            println!("# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0-or-later");
            for command in syd::config::PROFILE_SILENT {
                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-or-later");
            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-or-later");
            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-or-later");
            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-or-later");
            for command in syd::config::PROFILE_TRACE {
                println!("{command}");
            }
        }
        "user" => {
            let uid = Uid::effective();
            let gid = Gid::effective();
            let name = syd::get_user_name(uid);
            let mut home = syd::get_user_home(&name);

            println!("# Syd profile: User \"{name}\"");
            println!(
                "# Number of rules: {}",
                syd::config::PROFILE_USER.len().saturating_add(9)
            );
            println!("# Copyright (c) 2023 Ali Polatel <alip@chesswob.org>");
            println!("# SPDX-License-Identifier: GPL-3.0-or-later");

            // Step 1: Static configuration defined at compile time.
            for command in syd::config::PROFILE_USER {
                println!("{command}");
            }

            // Step 2: Dynamic, user-specific configuration.
            // Keep in sync with Sandbox::parse_profile()
            println!("allow/lock/write+{home}");

            if !home.ends_with('/') {
                home.push('/');
            }

            println!("allow/read+{home}***");
            println!("allow/stat+{home}***");
            println!("allow/write+{home}**");
            println!("allow/exec+{home}**");
            println!("allow/net/bind+{home}**");
            println!("allow/net/connect+{home}**");
            println!("allow/read+/run/user/{uid}/**");
            println!("allow/write+/run/user/{uid}/**");
            println!("tpe/negate:1");
            println!("tpe/user_owned:1");
            println!("tpe/gid:{gid}");
        }
        _ => return Err(Errno::EINVAL.into()),
    }

    Ok(())
}
