#!/bin/bash

#############################################################
#### fapolicyd Log Analyzer Script                       ####
#### Parses denial logs and suggests fix commands        ####
####                                                     ####
#### Author: Phil Connor                                 ####
#### Contact: contact@mylinux.work                       ####
#### License: MIT                                        ####
#### Version 1.00                                        ####
####                                                     ####
#### To use this script chmod it to 755                  ####
#### or simply type bash <filename.sh>                   ####  
#############################################################

# ── Colors ────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # No Color

# ── Defaults ──────────────────────────────────────────────
MODE="recent"
OUTPUT_FILE=""
QUIET=0
TOTAL_DENIALS=0
UNIQUE_FILES=0
SUGGESTED_FIXES=0

# ── Functions ─────────────────────────────────────────────

usage() {
    echo -e "${BOLD}fapolicyd Log Analyzer${NC}"
    echo ""
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  --help          Show this help message"
    echo "  --recent        Analyze denials from the last hour only (default)"
    echo "  --all           Analyze all denials in the log"
    echo "  --output FILE   Save suggested fixes to FILE"
    echo "  --quiet         Show suggestions only, suppress raw denial lines"
    echo ""
    echo "Examples:"
    echo "  sudo bash $0 --recent"
    echo "  sudo bash $0 --all --output fixes.txt"
    echo "  sudo bash $0 --quiet --output /tmp/fixes.txt"
    exit 0
}

check_root() {
    if [[ $EUID -ne 0 ]]; then
        echo -e "${RED}Error: This script must be run as root.${NC}"
        echo "Please run with: sudo bash $0"
        exit 1
    fi
}

check_fapolicyd() {
    if ! command -v fapolicyd-cli &>/dev/null; then
        echo -e "${RED}Error: fapolicyd does not appear to be installed.${NC}"
        echo -e "${YELLOW}Install with: dnf install fapolicyd${NC}"
        exit 1
    fi

    if ! systemctl is-active --quiet fapolicyd 2>/dev/null; then
        echo -e "${YELLOW}Warning: fapolicyd service is not currently running.${NC}"
        echo -e "${CYAN}Continuing to analyze existing log entries...${NC}"
        echo ""
    fi
}

output_line() {
    local line="$1"
    echo -e "$line"
    if [[ -n "$OUTPUT_FILE" ]]; then
        echo -e "$line" | sed 's/\x1b\[[0-9;]*m//g' >> "$OUTPUT_FILE"
    fi
}

# ── fapolicyd Analysis ───────────────────────────────────

parse_fapolicyd_denial() {
    local line="$1"

    local dec perm fname exe trust pid
    dec=$(echo "$line" | grep -oP 'dec=\K[^ ]+')
    perm=$(echo "$line" | grep -oP 'perm=\K[^ ]+')
    fname=$(echo "$line" | grep -oP 'fname=\K[^ ]+')
    exe=$(echo "$line" | grep -oP 'exe=\K[^ ]+')
    trust=$(echo "$line" | grep -oP 'trust=\K[^ ]+')
    pid=$(echo "$line" | grep -oP 'pid=\K[^ ]+')

    if [[ $QUIET -eq 0 ]]; then
        output_line "${RED}DENIAL:${NC} $line"
    fi

    [[ -n "$dec" ]]   && output_line "${CYAN}  Decision:${NC}      $dec"
    [[ -n "$perm" ]]  && output_line "${CYAN}  Permission:${NC}    $perm"
    [[ -n "$fname" ]] && output_line "${CYAN}  File:${NC}          $fname"
    [[ -n "$exe" ]]   && output_line "${CYAN}  Executable:${NC}    $exe"
    [[ -n "$trust" ]] && output_line "${CYAN}  Trust status:${NC}  $trust"
    [[ -n "$pid" ]]   && output_line "${CYAN}  PID:${NC}           $pid"

    suggest_fapolicyd_fix "$fname" "$exe" "$perm" "$trust"
    output_line ""
}

suggest_fapolicyd_fix() {
    local fname="$1" exe="$2" perm="$3" trust="$4"

    ((SUGGESTED_FIXES++))

    if [[ -n "$fname" ]]; then
        # Check current trust status
        output_line "${GREEN}  Suggested fixes:${NC}"

        # Trust the file
        output_line "${GREEN}    1. Add file to trust database:${NC}"
        output_line "${GREEN}       fapolicyd-cli --file add ${fname}${NC}"
        output_line "${GREEN}       fapolicyd-cli --update${NC}"

        # Check trust
        output_line "${GREEN}    2. Verify trust status:${NC}"
        output_line "${GREEN}       fapolicyd-cli --check-path ${fname}${NC}"

        # If the file is a script or binary from a known package
        if command -v rpm &>/dev/null; then
            local pkg
            pkg=$(rpm -qf "$fname" 2>/dev/null)
            if [[ $? -eq 0 && -n "$pkg" ]]; then
                output_line "${CYAN}    Note: File belongs to package: ${pkg}${NC}"
                output_line "${YELLOW}    If the file was modified after install, consider:${NC}"
                output_line "${GREEN}       rpm --restore ${pkg}${NC}"
                output_line "${GREEN}       fapolicyd-cli --update${NC}"
            fi
        fi

        # If it looks like a shared library
        if [[ "$fname" == *.so* ]]; then
            output_line "${YELLOW}    Library denial — also check:${NC}"
            output_line "${GREEN}       ldconfig${NC}"
            output_line "${GREEN}       fapolicyd-cli --update${NC}"
        fi
    fi

    # Suggest rule-based approach
    if [[ -n "$exe" && -n "$perm" ]]; then
        output_line "${GREEN}    3. Or add a custom rule in /etc/fapolicyd/rules.d/:${NC}"
        output_line "${GREEN}       allow ${perm} exe=${exe} : all${NC}"
    fi
}

categorize_fapolicyd_denial() {
    local line="$1"
    local perm fname

    perm=$(echo "$line" | grep -oP 'perm=\K[^ ]+')
    fname=$(echo "$line" | grep -oP 'fname=\K[^ ]+')

    case "$perm" in
        execute)
            if [[ "$fname" == *.so* ]]; then
                echo "library"
            else
                echo "execute"
            fi
            ;;
        open)
            echo "open"
            ;;
        *)
            echo "other"
            ;;
    esac
}

analyze_fapolicyd() {
    output_line "${BOLD}═══════════════════════════════════════════════════${NC}"
    output_line "${BOLD}  fapolicyd Log Analysis${NC}"
    output_line "${BOLD}═══════════════════════════════════════════════════${NC}"
    output_line ""

    # Show daemon status
    local daemon_status
    daemon_status=$(systemctl is-active fapolicyd 2>/dev/null)
    output_line "${CYAN}fapolicyd status:${NC} $daemon_status"

    # Show integrity setting
    if [[ -f /etc/fapolicyd/fapolicyd.conf ]]; then
        local integrity
        integrity=$(grep -oP '^\s*integrity\s*=\s*\K.*' /etc/fapolicyd/fapolicyd.conf 2>/dev/null)
        [[ -n "$integrity" ]] && output_line "${CYAN}Integrity mode:${NC}  $integrity"
    fi

    # Show trust database stats
    if command -v fapolicyd-cli &>/dev/null; then
        local trust_count
        trust_count=$(fapolicyd-cli --dump-db 2>/dev/null | wc -l)
        [[ -n "$trust_count" ]] && output_line "${CYAN}Trusted files:${NC}   $trust_count"
    fi
    output_line ""

    # Gather denials from audit log
    local denials=""

    if [[ ! -f /var/log/audit/audit.log ]]; then
        output_line "${RED}Error: Cannot find /var/log/audit/audit.log${NC}"
        output_line "${YELLOW}Ensure auditd is running: systemctl start auditd${NC}"
        return
    fi

    if [[ "$MODE" == "recent" ]]; then
        if command -v ausearch &>/dev/null; then
            denials=$(ausearch -m FANOTIFY -ts recent 2>/dev/null | grep "type=FANOTIFY")
        fi
        # Fallback to manual log parsing
        if [[ -z "$denials" ]]; then
            local one_hour_ago
            one_hour_ago=$(date -d '1 hour ago' '+%s' 2>/dev/null)
            if [[ -n "$one_hour_ago" ]]; then
                denials=$(awk -v cutoff="$one_hour_ago" '
                    /type=FANOTIFY/ && /dec=deny/ {
                        match($0, /msg=audit\(([0-9]+)\./, arr)
                        if (arr[1] >= cutoff) print
                    }
                ' /var/log/audit/audit.log)
            fi
        fi
    else
        denials=$(grep "type=FANOTIFY" /var/log/audit/audit.log | grep "dec=deny")
    fi

    if [[ -z "$denials" ]]; then
        output_line "${GREEN}No fapolicyd denials found.${NC}"
        output_line ""
        return
    fi

    # Group denials by category
    declare -A categories
    local denial_count=0
    local -A seen_files

    while IFS= read -r line; do
        [[ -z "$line" ]] && continue
        ((denial_count++))
        local category
        category=$(categorize_fapolicyd_denial "$line")
        categories["$category"]+="$line"$'\n'

        local f
        f=$(echo "$line" | grep -oP 'fname=\K[^ ]+')
        [[ -n "$f" ]] && seen_files["$f"]=1
    done <<< "$denials"

    TOTAL_DENIALS=$denial_count
    UNIQUE_FILES=${#seen_files[@]}

    # Display grouped results
    for category in "execute" "library" "open" "other"; do
        if [[ -n "${categories[$category]}" ]]; then
            local label
            case "$category" in
                execute) label="Execution Denials" ;;
                library) label="Library Load Denials" ;;
                open)    label="File Open Denials" ;;
                other)   label="Other Denials" ;;
            esac

            output_line "${BOLD}── ${label} ──────────────────────────────────${NC}"
            output_line ""

            while IFS= read -r denial_line; do
                [[ -z "$denial_line" ]] && continue
                parse_fapolicyd_denial "$denial_line"
            done <<< "${categories[$category]}"
        fi
    done

    # Show bulk fix suggestions
    if [[ ${#seen_files[@]} -gt 0 ]]; then
        output_line "${BOLD}── Bulk Fix Commands ────────────────────────────${NC}"
        output_line ""
        output_line "${YELLOW}To trust all denied files at once:${NC}"
        for f in "${!seen_files[@]}"; do
            output_line "${GREEN}  fapolicyd-cli --file add ${f}${NC}"
        done
        output_line "${GREEN}  fapolicyd-cli --update${NC}"
        output_line ""
    fi

    # Rule file reference
    output_line "${BOLD}── Rule File Reference ──────────────────────────${NC}"
    output_line ""
    output_line "${CYAN}Rules are loaded from:${NC}"
    if [[ -d /etc/fapolicyd/rules.d ]]; then
        output_line "  /etc/fapolicyd/rules.d/ (drop-in directory)"
        local rule_files
        rule_files=$(ls /etc/fapolicyd/rules.d/ 2>/dev/null)
        if [[ -n "$rule_files" ]]; then
            output_line "${CYAN}  Current rule files:${NC}"
            while IFS= read -r rf; do
                output_line "    $rf"
            done <<< "$rule_files"
        fi
    fi
    if [[ -f /etc/fapolicyd/fapolicyd.rules ]]; then
        output_line "  /etc/fapolicyd/fapolicyd.rules (compiled rules)"
    fi
    output_line ""
    output_line "${YELLOW}After making changes, restart the daemon:${NC}"
    output_line "${GREEN}  systemctl restart fapolicyd${NC}"
    output_line ""
}

# ── Summary ───────────────────────────────────────────────

print_summary() {
    output_line "${BOLD}═══════════════════════════════════════════════════${NC}"
    output_line "${BOLD}  Summary${NC}"
    output_line "${BOLD}═══════════════════════════════════════════════════${NC}"
    output_line ""
    output_line "  Total denials found:     ${BOLD}${TOTAL_DENIALS}${NC}"
    output_line "  Unique files denied:     ${BOLD}${UNIQUE_FILES}${NC}"
    output_line "  Suggested fixes:         ${BOLD}${SUGGESTED_FIXES}${NC}"
    output_line ""

    if [[ -n "$OUTPUT_FILE" ]]; then
        output_line "${GREEN}Suggestions saved to: ${OUTPUT_FILE}${NC}"
        output_line ""
    fi
}

# ── Parse Arguments ───────────────────────────────────────

while [[ $# -gt 0 ]]; do
    case "$1" in
        --help|-h)
            usage
            ;;
        --recent)
            MODE="recent"
            shift
            ;;
        --all)
            MODE="all"
            shift
            ;;
        --output)
            if [[ -z "$2" || "$2" == --* ]]; then
                echo -e "${RED}Error: --output requires a filename argument.${NC}"
                exit 1
            fi
            OUTPUT_FILE="$2"
            shift 2
            ;;
        --quiet|-q)
            QUIET=1
            shift
            ;;
        *)
            echo -e "${RED}Unknown option: $1${NC}"
            echo "Use --help for usage information."
            exit 1
            ;;
    esac
done

# ── Main ──────────────────────────────────────────────────

check_root

# Clear output file if specified
if [[ -n "$OUTPUT_FILE" ]]; then
    > "$OUTPUT_FILE"
fi

echo -e "${BOLD}fapolicyd Log Analyzer v1.00${NC}"
echo -e "${CYAN}Mode: ${MODE}${NC}"
echo ""

check_fapolicyd
analyze_fapolicyd
print_summary
