#!/bin/sh -fu
#
# Copyright (C) 2020  Denis Medvedev.
# Copyright (C) 2024  Paul Wolneykien.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

OSEC_CONF_DIR="${OSEC_CONF_DIR:-/etc/osec}"
LOGROTATE_DIR="${LOGROTATE_DIR:-/etc/logrotate.d}"

LOG_FACILITY=user
BASE_LOG_PRIORITY=info
ALERT_LOG_PRIORITY=alert
OSEC_GROUP=osec
BASE_PROFILE='integalert'
LOG_TAG_BASE='integalert'
ENABLE_TRIGGERS=no

[ ! -e "$OSEC_CONF_DIR"/integalert.conf ] || . "$OSEC_CONF_DIR"/integalert.conf

# Don't read /etc/sysconfig/integalert in build mode.
if [ "$OSEC_CONF_DIR" = '/etc/osec' ]; then
    [ ! -e /etc/sysconfig/integalert ] || . /etc/sysconfig/integalert
fi

PKGLIBEXECDIR='/usr/lib/integalert'
REPORT_LOG_BASE='/var/log/integalert'
BASE_DATABASE_DIR='/var/lib/integalert'
TARGET_OSEC_CONF_DIR='/etc/osec'
RUNDIR_HOME=/run/integalert

## Add other profiles here
KNOWN_SUB_PROFILES='main container vm'

PROG="${0##*/}"
VERSION="0.5.1"

# Search and append new profiles to known.

find_subprofiles() {
    find "$OSEC_CONF_DIR" -mindepth 2 -maxdepth 2 \
	 \( -path "*/${BASE_PROFILE}_*/dirs.conf" -o \
	    -path "*/${BASE_PROFILE}_*/files.list" \) \
	 -printf '%h\n' | \
	while read -r dir; do
	    #shellcheck disable=SC2295
	    echo "${dir##*/${BASE_PROFILE}_}"
	done
}

_sorted_subprofiles() {
    local main="${KNOWN_SUB_PROFILES%%[ 	]*}"
    (
	echo "$main"
	(
	    echo "${KNOWN_SUB_PROFILES#*[ 	]}" | tr '[:space:]' '\n'
	    find_subprofiles | grep -e '.' | grep -v "$main"
	) | sort -u
    ) | tr '\n' ' '
}
sorted_subprofiles() {
    local subprofiles="$(_sorted_subprofiles)"
    echo "${subprofiles% }"
}

KNOWN_SUB_PROFILES="$(sorted_subprofiles)"

### Main

show_usage() {
    echo "Usage: $PROG [$(echo "$KNOWN_SUB_PROFILES" | tr ' ' '|')] [fix|check|configure]" >&2
    exit "${1:-0}"
}

TEMP="$(getopt -n "$PROG" -o hV -l help,version -- "$@")" || show_usage 1
eval set -- "$TEMP"

while :; do
    case "$1" in
        -h|--help)
	    show_usage
            ;;
	-V|--version)
	    cat <<EOF
$VERSION 2024
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
EOF
	    exit 0
	    ;;
        --)
	    shift
	    break
            ;;
        *)
	    message "$PROG: unrecognized option: $1" >&2
	    show_usage 1
            ;;
    esac
    shift
done

action=
parse_action() {
    case "$1" in
	fix|check|configure)
	    action="$1"
	    ;;
	*)
	    return 1
    esac

    return 0
}

SUBPROFILE=
if [ $# -eq 0 ]; then
    SUBPROFILE='main'
else
    parse_action "$1" ||:
    if [ -n "$action" ]; then
	SUBPROFILE='main'
    else
	if echo "$KNOWN_SUB_PROFILES" | grep -qFw -- "$1"; then
	    SUBPROFILE="$1"
	else
	    echo "Error! Unknown profile: $1" >&2
	    show_usage 1
	fi
    fi
fi

if [ $# -gt 1 ]; then
    if ! parse_action "$2"; then
	echo "Error! Unknown mode: $2" >&2
	show_usage 1
    fi
fi

[ -n "$action" ] || action='check'

case "$SUBPROFILE" in
    main)
	PROFILE="$BASE_PROFILE"
	;;
    *)
	PROFILE="${BASE_PROFILE}_$SUBPROFILE"
	;;
esac

if [ -z "$PROFILE" ]; then
    echo "BUG!: Empty profile!" >&2
    exit 2
fi

if ! parse_action "$action"; then
    echo "BUG!: Unexpected action '$action'!" >&2
    exit 2
fi

if [ ! -d "$OSEC_CONF_DIR/$PROFILE" ]; then
    echo "Create osec profile \"$PROFILE\"" >&2
    mkdir -p "$OSEC_CONF_DIR/$PROFILE"
fi

if ! [ -e "$OSEC_CONF_DIR/$PROFILE/dirs.conf" -o \
       -e "$OSEC_CONF_DIR/$PROFILE/files.list" ]
then
    case "$SUBPROFILE" in
	main)
	    echo "Warning! Initializing $OSEC_CONF_DIR/$PROFILE/dirs.conf with $OSEC_CONF_DIR/dirs.conf" >&2
	    cat "$OSEC_CONF_DIR/dirs.conf" >"$OSEC_CONF_DIR/$PROFILE/dirs.conf"
	    ;;
	*)
	    touch "$OSEC_CONF_DIR/$PROFILE/dirs.conf"
	    ;;
    esac
fi

if ! [ -s "$OSEC_CONF_DIR/$PROFILE/dirs.conf" -o \
       -s "$OSEC_CONF_DIR/$PROFILE/files.list" ]
then
    echo "Warning! $OSEC_CONF_DIR/$PROFILE/dirs.conf is empty! Please, fill it with a directory list corresponding to your setup. Alternatively, use files.list to enable virtual directory mode." >&2
    exit 0
fi

if [ ! -e "$OSEC_CONF_DIR/$PROFILE/exclude.conf" ]; then
    case "$SUBPROFILE" in
	main)
	    if [ -e "$OSEC_CONF_DIR/exclude.conf" ]; then
		echo "Warning! Initializing $OSEC_CONF_DIR/$PROFILE/exclude.conf with $OSEC_CONF_DIR/exclude.conf" >&2
		cat "$OSEC_CONF_DIR/exclude.conf" >"$OSEC_CONF_DIR/$PROFILE/exclude.conf"
	    else
		touch "$OSEC_CONF_DIR/$PROFILE/exclude.conf"
	    fi
	    ;;
	*)
	    touch "$OSEC_CONF_DIR/$PROFILE/exclude.conf"
	    ;;
    esac
fi

case "$SUBPROFILE" in
    main)
	REPORT_LOG="$REPORT_LOG_BASE/lastlog"
	LOG_TAG="$LOG_TAG_BASE"
	;;
    *)
	REPORT_LOG="$REPORT_LOG_BASE/lastlog_$SUBPROFILE"
	LOG_TAG="${LOG_TAG_BASE}_$SUBPROFILE"
	;;
esac

DATABASE_DIR="$BASE_DATABASE_DIR/$SUBPROFILE"

if [ ! -e "$OSEC_CONF_DIR/$PROFILE/pipe.conf" ]; then
    cat >"$OSEC_CONF_DIR/$PROFILE/pipe.conf" <<EOF
export PROFILE='$PROFILE'
export SUBPROFILE='$SUBPROFILE'
export LOG_TAG='$LOG_TAG'
export REPORT_LOG='$REPORT_LOG'
export LOG_FACILITY='$LOG_FACILITY'
export BASE_LOG_PRIORITY='$BASE_LOG_PRIORITY'
export ALERT_LOG_PRIORITY='$ALERT_LOG_PRIORITY'

EXCLUDE_FILE='$TARGET_OSEC_CONF_DIR/$PROFILE/exclude.conf'
IMMUTABLE_DATABASE=\${INTEGALERT_READONLY:-yes}
SEND_PIPE='$PKGLIBEXECDIR/sender'
EOF

    cat >>"$OSEC_CONF_DIR/$PROFILE/pipe.conf" <<EOF

if [ -e '$RUNDIR_HOME/$SUBPROFILE/files.list' ]; then
    DATABASE_FILE='$DATABASE_DIR/$SUBPROFILE.cdb'
    VIRTUAL_DIR_FILE='$RUNDIR_HOME/$SUBPROFILE/files.list'
else
    DATABASE_DIR='$DATABASE_DIR'
    DIRS_FILE='$TARGET_OSEC_CONF_DIR/$PROFILE/dirs.conf'
fi
EOF

    ## Add a profile-specific things for pipe.conf here
    case "$SUBPROFILE" in
	vm)
            cat <<EOF
IGNORE_FIELDS=inode
EOF
	    ;;
    esac >>"$OSEC_CONF_DIR/$PROFILE/pipe.conf"
fi

if [ ! -e "$OSEC_CONF_DIR/$PROFILE/sender.conf" ]; then
    cat >"$OSEC_CONF_DIR/$PROFILE/sender.conf" <<EOF
# The default values are taken from
# $TARGET_OSEC_CONF_DIR/integalert.conf
# and /etc/sysconfig/integalert files.

#LOG_FACILITY='${LOG_FACILITY:-user}'
#BASE_LOG_PRIORITY='${BASE_LOG_PRIORITY:-info}'
#ALERT_LOG_PRIORITY='${ALERT_LOG_PRIORITY:-alert}'
EOF
fi

if [ ! -e "$LOGROTATE_DIR/$LOG_TAG.conf" ]; then
    cat >"$LOGROTATE_DIR/$LOG_TAG.conf" <<EOF
"${REPORT_LOG%/*}/$LOG_TAG.log" {
    rotate 100
    weekly
    missingok
    compress
}
EOF
fi

case "$action" in
    configure)
	exit 0
	;;
    fix)
	IMMUTABLE_DATABASE=no
	;;
    *)
	IMMUTABLE_DATABASE=yes
	;;
esac

mkdir -p "${REPORT_LOG%/*}"

if [ ! -e "$OSEC_CONF_DIR/$PROFILE/pipe.conf" ]; then
    echo "BUG!: OSEC profile $PROFILE doesn't exist!" >&2
    exit 2
fi

mkdir -p "$DATABASE_DIR"
chgrp "$OSEC_GROUP" "$DATABASE_DIR"
chmod 0775 "$DATABASE_DIR"

INTEGALERT_TMPDIR="$(mktemp -d --tmpdir integalert.XXXX)"
#shellcheck disable=SC2064
trap "rm -rf '$INTEGALERT_TMPDIR'" EXIT

if [ -e "$OSEC_CONF_DIR/$PROFILE/files.list" ]; then
    mkdir -p "$RUNDIR_HOME/$SUBPROFILE"
    #shellcheck disable=SC2002
    cat "$OSEC_CONF_DIR/$PROFILE/files.list" | \
	while read -r f; do
	    find "${f%/*}" -mindepth 1 -maxdepth 1 \
		 -name "${f##*/}" 2>/dev/null | \
		while read -r t; do
		    find "$t" \( -type f -o -type l \) \
			 2>/dev/null
		done
	done >"$RUNDIR_HOME/$SUBPROFILE/files.list"
else
    rm -f "$RUNDIR_HOME/$SUBPROFILE/files.list"
fi

export IMMUTABLE_DATABASE
export INTEGALERT_READONLY=$IMMUTABLE_DATABASE
export INTEGALERT_ACTION="$action"
export INTEGALERT_TMPDIR
/usr/share/osec/osec.cron "$PROFILE"
ret=$?

case "$SUBPROFILE" in
    main)
	for_clause=
	;;
    *)
	for_clause=" for '$SUBPROFILE'"

	;;
esac

cat "$REPORT_LOG" >>"${REPORT_LOG%/*}/$LOG_TAG.log"

case "$action" in
    fix)
	if [ $ret -eq 0 ]; then
            echo "Integrity database$for_clause updated." >&2
        else
            echo "Error updating integrity database$for_clause!" >&2
        fi

        exit $ret
        ;;
esac

_logger() {
    (
	# Read sender.conf with logging properties defined:
	. "$OSEC_CONF_DIR/$PROFILE/sender.conf"

	priority="$1"; shift

	case "$priority" in
	    info)
		priority="$BASE_LOG_PRIORITY"
		;;
	    *)
		priority="$ALERT_LOG_PRIORITY"
		;;
	esac

	logger -t "$LOG_TAG" -p "$LOG_FACILITY.$priority" "$@"
    )
}

if [ $ret -eq 0 ] && grep -q "(chg=0,add=0,del=0)" "$REPORT_LOG"
then
    echo "Integrity check$for_clause OK." >&2
    _logger info "Integrity check$for_clause OK"
else
    echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" >&2
    echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" >&2
    echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" >&2
    echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" >&2
    echo "Integrity check failure$for_clause!" >&2
    echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" >&2
    echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" >&2
    echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" >&2
    echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" >&2

    _logger alert "Integrity check failure$for_clause!"

    triggers_enabled=
    if [ "$ENABLE_TRIGGERS" -ne 0 ] 2>/dev/null
    then
	triggers_enabled=1
    elif ! [ "$ENABLE_TRIGGERS" -eq 0 ] 2>/dev/null
    then
	case "$ENABLE_TRIGGERS" in
	    [Yy]|[Yy][Ee][Ss]|[Oo][Nn]|[Tt][Rr][Uu][Ee]|\#t)
		triggers_enabled=1
		;;
	    [Nn]|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]|\#f)
		triggers_enabled=
		;;
	    *)
		triggers_enabled=1
		;;
	esac
    fi

    if [ -n "$triggers_enabled" -a \
	    -d "$TARGET_OSEC_CONF_DIR/$PROFILE"/trigger.d -a \
	    ! -e "$TARGET_OSEC_CONF_DIR/$PROFILE"/trigger.d/disable ]
    then
	echo "Run triggers in $TARGET_OSEC_CONF_DIR/$PROFILE/trigger.d..." >&2
	#shellcheck disable=SC2012
	ls "$TARGET_OSEC_CONF_DIR/$PROFILE/trigger.d" | while read -r f; do
	    case "$f" in
		*~|*.bak|*.bak.*|*.rpm*|.*)
		    continue
		    ;;
	    esac
	    [ -x "$TARGET_OSEC_CONF_DIR/$PROFILE/trigger.d/$f" ] || \
		continue
	    "$TARGET_OSEC_CONF_DIR/$PROFILE/trigger.d/$f" <"$REPORT_LOG"
	done
    fi

    ret=1
fi

exit $ret
