#!/usr/bin/env bash
#
# Copyright (C) 2004-2012,2014,2017-2019 Debian Logcheck Team
#                                        <logcheck@packages.debian.org>
# Copyright (C) 2002,2003                Jonathan Middleton <jjm@ixtab.org.uk>
# Copyright (C) 1999-2002                Rene Mayrhofer <rmayr@debian.org>
# Copyright (C) 1996-1997                Craig Rowland <crowland@psionic.com>

# This file is part of Logcheck

# Logcheck 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.

# Logcheck 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 Logcheck; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

if [ "$(id -u)" = 0 ]; then
    echo "logcheck should not be run as root. Use su to invoke logcheck:"
    echo "su -s /bin/bash -c \"/usr/sbin/logcheck${*:+ $*}\" logcheck"
    echo "Or use sudo: sudo -u logcheck logcheck${*:+ $*}."
    # you may want to uncomment that hack to let logcheck invoke itself.
    # su -s /bin/bash -c "$0 $*" logcheck
    exit 1
fi

if [ ! -f /usr/bin/lockfile-create ] || \
   [ ! -f /usr/bin/lockfile-remove ] || \
   [ ! -f /usr/bin/lockfile-touch ]; then
    echo "fatal: lockfile-progs is a prerequisite for logcheck, but was not found."
    exit 1
fi

# Set the umask
umask 077

# Set the flag variables
SYSTEM=0
SECURITY=0
ATTACK=0

# Set the getopts string
GETOPTS="c:dhH:l:L:D:m:opr:RsS:tTuvw"

# Set date for the email message
DATE="$(date +'%Y-%m-%d %H:%M %z')"

# Used by 'logcheck -v'
VERSION=unknown

# Set the default report level
REPORTLEVEL="server"

# default is to send mails to the local root user
SENDMAILTO="root"

# by default, mark the email as auto-generated
MIMECONSTRUCTARGS=(--header 'Auto-Submitted: auto-generated')

# Set the default subject lines
ATTACKSUBJECT="Security Alerts"
SECURITYSUBJECT="Security Events"
EVENTSSUBJECT="System Events"
ADDTAG="no"

# Default paths
RULEDIR="/etc/logcheck"
CONFFILE="/etc/logcheck/logcheck.conf"
STATEDIR="/var/lib/logcheck"
LOGFILES_LIST="/etc/logcheck/logcheck.logfiles"
LOGFILES_LIST_D="/etc/logcheck/logcheck.logfiles.d"
LOGFILE_FALLBACK="/var/log/syslog"
LOGTAIL="/usr/sbin/logtail2"
SYSLOG_SUMMARY="/usr/bin/syslog-summary"

# Defaults for options
INTRO=1
LOGCHECKDEBUG=0
MAILOUT=0
MAILASATTACH=0
MIMEENCODING=
NOCLEANUP=0
REBOOT=0
FQDN=0
SORTUNIQ=0
SUPPORT_CRACKING_IGNORE=0
SYSLOGSUMMARY=0
if [ -d "$RUNTIME_DIRECTORY" ] && [ -w "$RUNTIME_DIRECTORY" ]; then
	LOCKDIR="$RUNTIME_DIRECTORY"
elif [ -d "$XDG_RUNTIME_DIR" ] && [ -w "$XDG_RUNTIME_DIR" ]; then
	LOCKDIR="$XDG_RUNTIME_DIR/logcheck"
elif [ -d "$TMPDIR" ] && [ -w "$TMPDIR" ]; then
	LOCKDIR="$TMPDIR/logcheck"
else
	LOCKDIR="/tmp/logcheck"
fi
LOCKFILE="$LOCKDIR/logcheck"

# Allow globs to return zero files
shopt -s nullglob

# remove the lockfile
remove_lockfile(){
		if [ -n "$LOCK" ]; then
				debug "Killing lockfile-touch - $LOCK"
				kill "$LOCK" && unset LOCK
		fi

		if [ -f "$LOCKFILE.lock" ]; then
				debug "Removing lockfile: $LOCKFILE.lock"
				lockfile-remove "$LOCKFILE"
		fi
}

# cleanup() is called at the end to remove temporary files
cleanup() {
		remove_lockfile

		if [ -d "$TMPDIR" ]; then
				if [ "$NOCLEANUP" -eq 0 ];then
						debug "cleanup: Removing working dir: $TMPDIR"
						rm -r "$TMPDIR"
				else
						debug "cleanup: Not removing working dir: $TMPDIR"
				fi
		fi
		debug "cleanup: Done"
}

# debug() output (does nothing unless -d given, if -d, writes to stderr)
# First argument - string describing what is happening
# Second argument - file, whose contents are shown
debug() {
		local file="${2-}"
		local lines=0
		if [ "$LOGCHECKDEBUG" -eq 1 ]; then
				echo "DEBUG: [$(date)] $1" >&2
				if [ -n "$file" ]; then
						lines=$(wc -l "$file" | cut -d ' ' -f1)
						echo "--- START [ $file (LINES: $lines) ] ---" >&2
						cat "$file" >&2
						echo -e "--- END [ $file (LINES: $lines) ] ---\n" >&2
				fi
		fi
}

# Issue warnings on stdout (not used)
warn() {
		local message="$1"
		debug "Warning - $message"

		echo "Warning: $message"
		if [ -d "$TMPDIR" ]; then
				echo "$message" >> "$TMPDIR/warnings" \
						|| error "Could not append to $TMPDIR/warnings"
		fi
		return 0
}

# Mail error message to sysadmin
#  (Also show on stderr)
#  (If 2nd argument is 'noclean' then do not remove the lockfile)
error() {
		debug "error(): $*"
		local message="${1-}"
		echo "Error: $message" >&2

		if [ "${2-}" = "noclean" ]; then
				debug "error(): Not removing lockfile"
		else
				remove_lockfile
    fi

		if [ "$MAILOUT" -eq 0 ]; then
				{
						cat<<EOF
Logcheck failed: Your log entries may not have been checked.

Details:
$message

To identify the cause you may wish to:
${TMPDIR:+"- Check temporary directory: $TMPDIR"
}
- verify that the logcheck user can read all
logfiles specified in;
  /etc/logcheck/logcheck.logfiles
  /etc/logcheck/logcheck.logfiles.d/*.logfiles
- check the system has enough space; (df -h output follows):
$(df -h 2>&1|| :)
- check the settings (environment follows):
$(export 2>&1||:)
EOF
				} | mime-construct "${MIMECONSTRUCTARGS[@]}" \
								 --subject "Logcheck: $HOSTNAME $DATE exiting due to errors" "${ENCODING[@]}" \
								 --file - --to "$SENDMAILTO"
		fi
		exit 1
}

# Add an introduction to the report
setintro() {
		if [ -f "$RULEDIR/header.txt" ] && [ -r "$RULEDIR/header.txt" ] ; then
				debug "Adding a header to the report" "$RULEDIR/header.txt"
				cat "$RULEDIR/header.txt" >> "$TMPDIR/report" \
						|| error "Could not append header to $TMPDIR/report"
		else
				debug "Unable to add a header to the report: $RULEDIR/header.txt does not exist or could not be read"
		fi
}


# Add a footer to the report.
setfooter() {
		if [ -f "$RULEDIR/footer.txt" ] && [ -r "$RULEDIR/footer.txt" ] ; then
				cat "$RULEDIR/footer.txt" >> "$TMPDIR/report" \
						|| error "Could not append footer to $TMPDIR/report"
		fi
}


# Clean a directory of rules:
# - For every rules file in the directory, remove blank lines and comments
#    run-parts is used to list rules, so backup files are ignored
# - Store the results in a new directory (passed as the second argument)
# takes two args: directory to clean and output directory
cleanrules() {
		local dir="$1"
		local cleaned="$2"
		local rulefile x
		debug "cleanrules $dir => $cleaned"
		if [ -d "$dir" ]; then
				mkdir -p "$cleaned" \
								|| error "Could not make dir $cleaned (for cleaned rulefiles from $dir)"
				# report unreadable files - run-parts silently discards them
				for x in "$dir"/*; do
						if [ -f "$x" ] && [ ! -r "$x" ]; then
								error "Could not read $x"
						fi
				done
				for rulefile in $(run-parts --list "$dir"); do
						rulefile="$(basename "$rulefile")"
						if [ -f "${dir}/${rulefile}" ]; then
								debug "cleanrules: ${dir}/${rulefile} -> $cleaned/$rulefile"
								# pipe outut of 'grep' to 'cat' because we want a exit status of 0 even if grep did not find matches
								command grep -E --text -v '^[[:space:]]*$|^#' "$dir/$rulefile" \
										| cat >> "$cleaned/$rulefile" \
										|| error "Could not append to $cleaned/$rulefile"
						fi
				done
		elif [ -f "$dir" ]; then
				error "cleanrules: '$dir' is a file, not a directory"
		elif [ -z "$dir" ]; then
				error "cleanrules: called without argument"
		fi
}

# Add any events to the report
report() {
		if [ -s "$TMPDIR/checked" ]; then
				debug "report(): $*"
				printheader "$*" >> "$TMPDIR/report" \
						|| error "Could not append to report"
				if [ "$SYSLOGSUMMARY" -eq 1 ] && [ -x "$SYSLOG_SUMMARY" ]; then
						debug "report ($*): running syslog-summary on $TMPDIR/checked"
						"$SYSLOG_SUMMARY" "$TMPDIR/checked" | \
								command grep -Ev "^Summarizing " \
								| cat >> "$TMPDIR/report" \
								|| error "Could not append to report"
				else
						if [ "$SYSLOGSUMMARY" -eq 1 ] && [ ! -x "$SYSLOG_SUMMARY" ]; then
								debug "report ($*): WARNING: can't exec $SYSLOG_SUMMARY. Running without syslog-summary"
						fi
						debug "report ($*): Adding lines to $TMPDIR/report" "$TMPDIR/checked"
						cat "$TMPDIR/checked" >> "$TMPDIR/report" \
								|| error "Could not append to report"
				fi
				echo >> "$TMPDIR/report" \
						|| error "Could not append a blank line to the report"
				return 0
		else
				debug "report ($*): Nothing to report"
				return 1
		fi
}

# Add section headings to the report
printheader() {
		local char="="
		local header="$1"
		local number="${#header}"
		local line=""
		debug "Writing section heading '$header' to $TMPDIR/report"
		for _ in $(seq "$number"); do
				line="${line}${char}"
				if [ "$char" = "=" ]; then
						char="-"
				else
						char="="
				fi
		done
		echo "$header"
		echo "$line"
}

# Mail the report
sendreport() {
		local subject
		if [ "$REBOOT" -eq 1 ]; then
				subject="Reboot: $HOSTNAME $DATE $*"
		else
				subject="$HOSTNAME $DATE $*"
		fi
		if [ "$ADDTAG" = "yes" ]; then
				subject="[logcheck] $subject"
		fi

		if [ "$MAILOUT" -eq 1 ]; then
				debug "Sending report to STDOUT"
				cat "$TMPDIR/report"
				debug "Sent report to STDOUT"
		else
				debug "Sending report: '$subject' by email to $SENDMAILTO"
				if [ "$MAILASATTACH" -eq 1 ]; then
						debug "Sending report as attachment"
						mime-construct "${MIMECONSTRUCTARGS[@]}" --subject "$subject" "${ENCODING[@]}" \
													 --string "Report attached" --to "$SENDMAILTO" \
													 --attachment "logcheck_report" "${ENCODING[@]}" --file "$TMPDIR/report"
						return $?
				elif [ "$MAILASATTACH" -eq 2 ]; then
						debug "Sending report as gzip attachment"
						gzip --to-stdout "$TMPDIR/report" \
								| mime-construct "${MIMECONSTRUCTARGS[@]}" --subject "$subject" "${ENCODING[@]}" \
																 --string "Report attached" --to "$SENDMAILTO" \
																 --type "application/x-gzip" --attachment "logcheck_report.gz" \
																 --file -
						return $?
				else
						mime-construct "${MIMECONSTRUCTARGS[@]}" --subject "$subject" --to "$SENDMAILTO" "${ENCODING[@]}" \
													 --file "$TMPDIR/report"
				fi
		fi
}

# Grep the log output ($TMPDIR/logoutput-sorted) storing result in $TMPDIR/checked
# using:
#   $1 - directory of positive rules: any matches are added to the report
#   $2 - heading for matches
#   $3 - (optional) directory of ignore rules: filters out matches
#   $4 - (optional) directory of ignorehigher rules: filters out matches
greplogoutput() {
		local raise="$1"
		local sectionstring="$2"
		local ignore="$3"
		local ignorehigher="$4"

		local RETURN=1
		local ignore_rules grepfile

		debug "greplogoutput ($*): Starting"
		for grepfile in "$raise"/*; do
				debug "greplogoutput: Using $grepfile to find entries to report" "$grepfile"

				# Report entries that match
				command grep -E --text -f "$grepfile" "$TMPDIR/logoutput-sorted" \
						| cat > "$TMPDIR/checked" \
						|| error "Could not output to $TMPDIR/checked"

				debug "greplogoutput: After using $grepfile we have the following entries to report" \
							 "$TMPDIR/checked"

				# apply different 'ignore' rules
				if [ -s "$TMPDIR/checked" ]; then
						ignore_rules="$ignore/$(basename "$grepfile")"
						debug "Filtering reportable entries using '$ignore_rules' (if it exists)"
						if [ -n "$ignore" ] && [ -f "$ignore_rules" ]; then
								cleanchecked "$ignore_rules"
						else
								debug "(It does not exist)"
						fi

						ignore_rules="$ignore/logcheck-$(basename "$grepfile")"
						debug "Filtering entries using '$ignore_rules' (if it exists)"
						if [ -n "$ignore" ] && [ -f "$ignore_rules" ]; then
								cleanchecked "$ignore_rules"
						else
								debug "(It does not exist)"
						fi

						# If it's the logcheck file, we do something special
						if [ "$(basename "$grepfile")" = "logcheck" ]; then
								if [ -n "$ignore" ]; then
										# Ignore files _always_ impact matches from logcheck
										debug "Filtering entries using all rules in: $ignore (if it is set)"
										for file in "$ignore"/* ; do
												if [ -s "$TMPDIR/checked" ]; then
														cleanchecked "$ignore"
												fi
										done
								else
										debug "(It is not set)"
								fi

								# Remove any entries already reported under a separate section
								debug "Filtering using any file in $raise that does not start with 'logcheck'"
								for file in "$raise"/*; do
										if [ -s "$TMPDIR/checked" ]; then
												if ! [[ "$(basename "$file")" =~ ^logcheck* ]]; then
													 debug "Filtering using: $file"
													 cleanchecked "$file"
												fi
										fi
								done
						fi

						if [ -n "$ignorehigher" ]; then
								if [ -d "$ignorehigher" ] && [ -s "$TMPDIR/checked" ]; then
										debug "Filtering out already-reported entries found by 'higher ignore' dir $ignorehigher"
										cleanchecked "$ignorehigher"
								fi
						fi

						# Apply local rules before we report
						if [ -n "$ignore" ]; then
								debug "Filtering using: $ignore/local (if it exists)"
								if [ -f "$ignore/local" ] && [ -s "$TMPDIR/checked" ]; then
										cleanchecked "$ignore/local"
								else
										debug "(It does not exist)"
								fi

								# Now apply any local-* files
								debug "Filtering using: $ignore/local-*"
								for file in "$ignore"/local-* ; do
										if [ -f "$file" ] && [ -s "$TMPDIR/checked" ]; then
												cleanchecked "$file"
										fi
								done
						fi

						debug "greplogoutput: Finished all filters."
						if [ "$(basename "$grepfile")" = "logcheck" ]; then
								report "${sectionstring}" && RETURN=0
						else
								report "${sectionstring} for $(basename "$grepfile")" \
										&& RETURN=0
						fi
				fi
		done
		debug "greplogoutput: Finished, with the following entries remaining" "$TMPDIR/checked"
		return $RETURN
}

# Clean $TMPDIR/checked using a rules file (or directory of rules files) (passed as $1)
cleanchecked() {
		clean="$1"

		if [ -f "$clean" ]; then
				debug "cleanchecked - filtering using file: $clean" "$clean"
				command grep -E --text -v -f "$clean" "$TMPDIR/checked" \
						| cat >> "$TMPDIR/checked.1"  \
						|| error "Could not output to $TMPDIR/checked.1"
				debug "cleanchecked - after using $clean remaining lines are as follows:" "$TMPDIR/checked.1"
				mv "$TMPDIR/checked.1" "$TMPDIR/checked" \
						|| error "Could not move $TMPDIR/checked.1 to $TMPDIR/checked"
		elif [ -d "$clean" ]; then
				debug "cleanchecked - filtering using all files in dir: $clean"
				for file in "$clean"/*; do
						cleanchecked "$file"
				done
		else
				error "cleanchecked: Not a file or a directory $clean"
		fi
}

# Get the unseen part of a logfile (or the journal).
# $1 - log file to use
logoutput() {
		local file="$1"
		local JOURNALCTL="journalctl"
		local JOURNALCTL_OPTS=() OPTS=()
		local offsetfile offsettime

		# There are some problems with this section.
		debug "logoutput called with file: $file"
		if [ -f "$file" ]; then
				offsetfile="$STATEDIR/offset$(echo "$file" | tr / .)"
				debug "Running $LOGTAIL on $file"

				OPTS=( -f "$file" -o "$offsetfile" )
				if [ -n "${LOGTAIL_OPTS-}" ]; then
						OPTS+=("$LOGTAIL_OPTS")
				fi
				"$LOGTAIL" "${OPTS[@]}" >> "$TMPDIR/logoutput/$(basename "$file")" 2>&1 \
						|| error "Could not run logtail or save output"
		else
				if [ "$file" = "journal" ] && [ -x "$(command -v $JOURNALCTL)" ]; then
						offsetfile="$STATEDIR/offset.$file"
						offsettime=""
						if [ -f "$offsetfile" ]; then
								offsettime="--since=@$(stat -c %Y "$offsetfile")"
						else
								echo "This is the first time logcheck has checked the systemd journal." \
										 >> "$TMPDIR/report" || error "Could not write message about first-time check of journal to report"
								echo "Only recent entries (from the last 5 hours) will be checked" \
										 >> "$TMPDIR/report" || error "Could not write message about first-time check of journal to report"
								echo "If you do not wish to check the systemd journal, please see /etc/logcheck/logcheck.logfiles.d/journal.logfiles" \
										 >> "$TMPDIR/report" || error "Could not write message about first-time check of journal to report"
								offsettime="--since=-5h"
						fi
						debug "Running $JOURNALCTL ${JOURNALCTL_OPTS[*]} -q $offsettime"
						"$JOURNALCTL" "${JOURNALCTL_OPTS[@]}" --quiet "$offsettime" \
													>> "$TMPDIR/logoutput/$file" 2>&1 \
								|| error "Could not run journalctl or save output"
						if [ "$LOGTAIL_OPTS" != '-t' ]; then
							touch "$offsetfile"
						fi
				else
						echo "E: File could not be read: $file" >> "$TMPDIR/errors" \
								|| error "Could not output to $TMPDIR/errors"
				fi
		fi
}

# Show options
usage() {
		debug "usage: Printing usage and exiting"
		cat<<EOF
usage: logcheck [-c CFG] [-d] [-h] [-H HOST] [-l LOG] [-L CFG] [-D DIR] [-m MAIL] [-o]
                [-r DIR] [-s|-p|-w] [-R] [-S DIR] [-t] [-T] [-u]
 -c CFG       = override default configuration file
 -d           = debug mode
 -h           = print this usage information and exit
 -H HOST      = use this hostname in the subject of any generated mail
 -l LOG       = check the specified logfile
 -L CFG       = override default logfiles list
 -D DIR       = override default logfiles lists directory
 -m MAIL      = send the report to the specified recipient
 -o           = send the report to stdout, no mail will be sent
 -p           = use the "paranoid" runlevel
 -r DIR       = override default rules directory
 -R           = adds "Reboot:" to email subject
 -s           = use the "server" runlevel
 -S DIR       = override default state directory
 -t           = testing mode, don't update the logfile offsets
 -T           = do not remove the TMPDIR
 -u           = enable syslog-summary
 -v           = print version
 -w           = use the "workstation" runlevel
EOF
}

# Check the commandline options for a change to the config file option
while getopts "$GETOPTS" opt; do
		case "$opt" in
				c)
						debug "Setting CONFFILE to $OPTARG"
						CONFFILE="$OPTARG"
						if [ ! -r "$CONFFILE" ]; then
								error "Config file $CONFFILE is unreadable or does not exist"
						fi
						;;
				d)
						LOGCHECKDEBUG=1
						debug "Turning debug mode on"
						;;
				h)
						usage
						exit 0
						;;
				T)
						debug "Setting NOCLEANUP to 1"
						NOCLEANUP=1
						;;
				v)
						echo "logcheck $VERSION"
						exit 0
						;;
				\?)
						usage
						exit 1
						;;
		esac
done

# Now reset $OPTIND to 1
OPTIND=1

debug "Sourcing - $CONFFILE"

# Now source the config file - before things that should not be changed
if [ -r "$CONFFILE" ]; then
		# shellcheck source=/etc/logcheck/logcheck.conf
		. "$CONFFILE"
elif [ -f "$CONFFILE" ]; then
		error  "Config file $CONFFILE could not be read"
fi

# Ensure $INTRO is set correctly
if [ -z "$INTRO" ]; then
		INTRO=1
elif [ "$INTRO" = "no" ]; then
		INTRO=0
elif [ "$INTRO" = "yes" ]; then
		INTRO=1
fi

# Use sort -u or -k 1,3 -s
if [ "$SORTUNIQ" -eq 1 ];then
		SORT="sort -u"
else
		SORT="sort -k 1,3 -s"
fi

# Set the mime encoding if required (blank means auto-detected)
if [ -n "$MIMEENCODING" ]; then
		ENCODING=(--encoding "$MIMEENCODING")
else
		ENCODING=()
fi

# HOSTNAME is either 'fully qualified' or 'short'
if [ "$FQDN" -eq 1 ]; then
		HOSTNAME="$(hostname --fqdn 2>/dev/null)"
else
		HOSTNAME="$(hostname --short 2>/dev/null)"
fi

# Now check for the other options
while getopts "$GETOPTS" opt; do
		case "$opt" in
				H)
						debug "Setting HOSTNAME to $OPTARG"
						HOSTNAME="$OPTARG"
						;;
				l)
						debug "Setting LOGFILE to $OPTARG"
						LOGFILE="$OPTARG"
						;;
				L)
						debug "Setting LOGFILES_LIST to $OPTARG"
						LOGFILES_LIST="$OPTARG"
						;;
				D)
						debug "Setting LOGFILES_LIST_D to $OPTARG"
						LOGFILES_LIST_D="$OPTARG"
						;;
				m)
						debug "Setting SENDMAILTO to $OPTARG"
						SENDMAILTO="$OPTARG"
						;;
				o)
						debug "Setting MAILOUT to 1"
						MAILOUT="1"
						;;
				p)
						debug "Setting REPORTLEVEL to paranoid"
						REPORTLEVEL="paranoid"
						;;
				r)
						debug "Setting RULEDIR to $OPTARG"
						RULEDIR="$OPTARG"
						;;
				R)
						debug "Setting REBOOT to 1"
						REBOOT=1
						;;
				s)
						debug "Setting REPORTLEVEL to server"
						REPORTLEVEL="server"
						;;
				S)
						debug "Setting STATEDIR to $OPTARG"
						STATEDIR="$OPTARG"
						;;
				u)
						debug "Setting SYSLOGSUMMARY to 1"
						SYSLOGSUMMARY="1"
						;;
				t)
						debug "Setting LOGTAIL_OPTS to -t"
						LOGTAIL_OPTS='-t'
						;;
				w)
						debug "Setting REPORTLEVEL to workstation"
						REPORTLEVEL="workstation"
						;;
				\?)
						usage
						exit 1
						;;
		esac
done
debug "Finished getopts $GETOPTS"
shift $((OPTIND - 1))

if [ "$REPORTLEVEL" = "workstation" ]; then
		REPORTLEVELS="workstation server paranoid"
elif [ "$REPORTLEVEL" = "server" ]; then
		REPORTLEVELS="server paranoid"
elif [ "$REPORTLEVEL" = "paranoid" ]; then
		REPORTLEVELS="paranoid"
else
		error "REPORTLEVEL is set to an unknown value" "noclean"
fi
debug "Using rules from the following REPORTLEVELs: $REPORTLEVELS"

trap 'cleanup' 0

debug "Setting lockfile: $LOCKFILE.lock"
if [ ! -d "$LOCKDIR" ]; then
		mkdir -m 0755 "$LOCKDIR"
fi
lockfile-create --retry 1 "$LOCKFILE" > /dev/null 2>&1


if [ $? -eq 1 ]; then
		trap 0
		if [ -e "${LOCKFILE}.lock" ]; then
				error "Another logcheck process is still running" "noclean"
		else
				error "Failed to get lockfile: $LOCKFILE.lock" "noclean"
		fi
else
		debug "Running lockfile-touch $LOCKFILE.lock"
		lockfile-touch "$LOCKFILE" &
		LOCK="$!"
fi

# Create a secure temporary working directory (or exit)
TMPDIR="$(mktemp -d -p "${TMP:-/tmp}" logcheck.XXXXXX)" \
		|| TMPDIR="$(mktemp -d -p /var/tmp logcheck.XXXXXX)" \
		|| error "Could not create temporary directory"

debug "Using working dir: $TMPDIR"

# Clean the rulefiles into $TMPDIR
cleanrules "$RULEDIR/cracking.d" "$TMPDIR/cracking"
cleanrules "$RULEDIR/violations.d" "$TMPDIR/violations"
cleanrules "$RULEDIR/violations.ignore.d" "$TMPDIR/violations-ignore"

# Clean the rulefiles into $TMPDIR/ignore
for level in $REPORTLEVELS; do
		cleanrules "$RULEDIR/ignore.d.$level" "$TMPDIR/ignore"
done

# The cracking.ignore.d directory is only used if
# SUPPORT_CRACKING_IGNORE is set to 1 in the configuration file.
# This is *only* for local admin use.
if [ "$SUPPORT_CRACKING_IGNORE" -eq 1 ]; then
		cleanrules "$RULEDIR/cracking.ignore.d" "$TMPDIR/cracking-ignore"
fi

# Get the list of log files to check
# Handle log rotation correctly (idea taken from Wiktor Niesiobedzki)
mkdir "$TMPDIR/logoutput" \
    || error "Could not create $TMPDIR/logoutput"
LOGFILES=("$LOGFILES_LIST" "$LOGFILES_LIST_D"/*.logfiles)
if [ -z "${LOGFILE-}" ] && [ "${#LOGFILES[@]}" != "0" ]; then
		for file_list in "${LOGFILES[@]}" ; do
				if [ -f  "$file_list" ] && [ -r "$file_list" ]; then
						SAVEIFS=$IFS; IFS=$(echo -en "\n\b");
            for file in $(command grep -E --text -v -h "(^#|^[[:space:]]*$)" "$file_list"); do
								logoutput "$file"
						done
						IFS=$SAVEIFS
        else
            error "$file_list does not exist or could not be read"
        fi
    done
elif [ -n "$LOGFILE" ]; then
		if [ -f "$LOGFILE" ] && [ -r "$LOGFILE" ]; then
				logoutput "$LOGFILE"
		else
				error "$LOGFILE does not exist or could not be read"
		fi
elif [ -f "$LOGFILE_FALLBACK" ] && [ -r "$LOGFILE_FALLBACK" ]; then
		logoutput "$LOGFILE_FALLBACK"
else
		error "Gave up: No logfile settings and unable to read fallback file $LOGFILE_FALLBACK"
fi

# First sort the logs to remove duplicate lines (including from different logfiles with
# the same lines) to reduce CPU and memory usage.
debug "Sorting logs"
$SORT "$TMPDIR/logoutput"/* | sed -e 's/[[:space:]]\+$//' > "$TMPDIR/logoutput-sorted" \
		|| error "Could not save sorted log content to $TMPDIR/logoutput-sorted"
debug "After sorting, we have the following log entries to check" "$TMPDIR/logoutput-sorted"

# See if the logoutput-sorted file actually has data to check,
#  if not, we have nothing to do: erase it and exit
if [ ! -s "$TMPDIR/logoutput-sorted" ] && [ ! -f "$TMPDIR/errors" ]; then
		debug "Nothing to report"
		exit 0
elif [ ! -s "$TMPDIR/logoutput-sorted" ] && [ -f "$TMPDIR/errors" ]; then
		error "$(< "$TMPDIR/errors")"
fi

if [ "$INTRO" -eq 1 ]; then
		setintro
else
		debug "Not adding a header to the report (INTRO=$INTRO)"
fi

if [ -f "$TMPDIR/errors" ]; then
		{
				cat<<EOF

$(< "$TMPDIR/errors")

EOF
		} >> "$TMPDIR/report" \
				|| error "Could not output errors to $TMPDIR/report"
fi

# Check for blatant cracking attempts
if [ -d "$TMPDIR/cracking" ]; then
		if [ "$SUPPORT_CRACKING_IGNORE" -eq 1 ]; then
				debug "Using cracking-ignore"
				if [ -d "$TMPDIR/cracking-ignore" ]; then
						greplogoutput "$TMPDIR/cracking" "$ATTACKSUBJECT" \
													"$TMPDIR/cracking-ignore" && ATTACK="1"
				fi
		else
				debug "Checking for cracking alerts ($ATTACKSUBJECT) using cracking.d"
				greplogoutput "$TMPDIR/cracking" "$ATTACKSUBJECT" \
						&& ATTACK="1"
				debug "Finished check for security alerts (ATTACK=$ATTACK)"
		fi
fi

# Check for security events
if [ -d "$TMPDIR/violations" ]; then
		debug "Checking for violations ($SECURITYSUBJECT) using violations.d"
		rm -f "$TMPDIR/checked"

		if [ "$ATTACK" -eq 1 ]; then
				greplogoutput "$TMPDIR/violations" "$SECURITYSUBJECT" \
											"$TMPDIR/violations-ignore" "$TMPDIR/cracking" && SECURITY="1"
		else
				greplogoutput "$TMPDIR/violations" "$SECURITYSUBJECT" \
											"$TMPDIR/violations-ignore" && SECURITY="1"
		fi
		debug "Finished check for violations (SECURITY=$SECURITY)"
fi

# Check for system events: Unlike the above, this filters _out_ patterns
debug "Starting check for system events"
cp "$TMPDIR/logoutput-sorted" "$TMPDIR/checked" \
		|| error "Could not copy $TMPDIR/logoutput-sorted to $TMPDIR/checked"
if [ -d "$TMPDIR/ignore" ]; then
		debug "Filtering using dir: $TMPDIR/ignore (which has files from ignore.d.*)"
		cleanchecked "$TMPDIR/ignore"
fi
if [ -s "$TMPDIR/checked" ]; then
		debug "Removing alerts from system events (as they were reported already)"
		cleanchecked "$TMPDIR/cracking"
fi
if [ -s "$TMPDIR/checked" ]; then
		debug "Removing violations from system events (as they were reported already)"
		cleanchecked "$TMPDIR/violations"
fi
report "$EVENTSSUBJECT" && SYSTEM="1"
debug "Finished check for system events (SYSTEM=$SYSTEM)"

# Add any warnings to report
if [ -f "$TMPDIR/warnings" ]; then
		printheader "Logcheck Warnings" >> "$TMPDIR/report" \
				|| error "Could not append warnings to report"
		cat "$TMPDIR/warnings" >> "$TMPDIR/report" || error "Could not append warnings to report"
		echo >> "$TMPDIR/report" || error "Could not append warnings to report"
fi

# Include the footer if required
if [ "$INTRO" -eq 1 ]; then
		debug "Adding the footer to the report"
		setfooter
else
		debug "Not adding a footer to the report (INTRO=$INTRO)"
fi

# If there are results, report them (mail or stdout)
if [ "$ATTACK" -eq 1 ]; then
		sendreport "$ATTACKSUBJECT"
elif [ "$SECURITY" -eq 1 ]; then
		sendreport "$SECURITYSUBJECT"
elif [ "$SYSTEM" -eq 1 ]; then
		sendreport "$EVENTSSUBJECT"
fi
