#!/bin/sh

# Shell script to check snapshots/log files for SCC.
# Copyright (C) 2001-2004 Open Challenge B.V.
# Copyright (C) 2004-2005 OpenEyeT Professional Services.
# Copyright (C) 2005-2009 QNH.
#
# 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, 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; see the file COPYING.
# If not, write to the Free Software Foundation,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Contact information: http://scc.qnh-infrastructure.nl

# SCC-release:	1.7.5
# file-version:	$Revision: 1.57 $


ProgName=${0##*/};			export ProgName

set -u

SCC_BIN=/opt/scc-srv/bin
SCC_TOP=/var/opt/scc-srv
export SCC_BIN SCC_TOP

export SCC_DATA=${SCC_TOP}/data
export SCC_TMP=${SCC_TOP}/tmp
export SCC_CONF=${SCC_TOP}/conf
export SCC_WWW=${SCC_DATA}/www

export TMPDIR=${SCC_TMP}
export TMP=${SCC_TMP}

export SHELL=/bin/sh
export LANG=C

PATH=/sbin:/usr/sbin:/usr/bin:/bin:${SCC_BIN};	export PATH

# Use the following utilities for SunOS.
if [ -x /usr/ccs/bin/what ]
then
	alias what=/usr/ccs/bin/what
fi
if [ -x /usr/xpg4/bin/awk ]
then
	alias awk=/usr/xpg4/bin/awk
fi
if [ -x /usr/xpg4/bin/grep ]
then
	alias grep=/usr/xpg4/bin/grep
fi
if [ -x /usr/xpg4/bin/tail ]
then
	alias tail=/usr/xpg4/bin/tail
fi

umask 077

export TMP_FILE=${SCC_TMP}/scc_rules_$$
export TMP_DIFF=${SCC_TMP}/scc_diff_$$
export TMP_SIGNAL=${SCC_TMP}/scc_signal_$$

trap "rm -f ${TMP_FILE} ${TMP_DIFF} ${TMP_SIGNAL}" 0
trap "exit 2" 1 2 3 15

scc_check_max_snap_age()
{
	# Arguments:
	# 1: <systems>
	#	comma separated list of systems or "all_systems"
	# 2: <max_snap_age>
	#	maximum allowed age of snapshots
	#
	# Output:
	# CheckSnapAge <system> last run on <last_run>

	day=$(date '+%d')
	month=$(date '+%m')
	year=$(date '+%Y')

	max_age="${2}"
	if [ ${max_age} -gt 28 ]
	then
		echo "${ProgName}: reducing limit for max age of snapshots from ${max_age} to 28" >&2
		max_age=28
	fi

	# Checking the age limit is done by subtracting the limit from the current date.
	if [ ${max_age} -lt ${day} ]
	then
		day=$(( ${day} - ${max_age} ))
	else
		if [ ${month} -eq 1 ]
		then
			year=$(( ${year} - 1 ))
			month=12
		else
			month=$(( ${month#0} - 1 ))
		fi
		last_day_prev_month=31
		case ${month} in
		4|6|9|11)	last_day_prev_month=30;;
		2)		last_day_prev_month=28;;
		esac
		remainder=$(( ${max_age} - ${day#0} ))
		day=$(( ${last_day_prev_month} - ${remainder} ))
	fi
	snap_age_limit=$(printf "%04d%02d%02d" ${year} ${month#0} ${day#0})

	if [ "${1}" != "all_systems" ]
	then
		check_sys="$(echo ${1} | sed -e 's/,/ /g')"
	else
		check_sys="${all_sys}"
	fi

	rm -f ${TMP_SIGNAL}
	for sys in ${check_sys}
	do
		last_run="$(tail -n 1 scc.${sys}.log 2>/dev/null | sed -e 's/:.*//' -e 's/-//g')"
		if [ -n "${last_run}" ]
		then
			if [ "${last_run}" -lt "${snap_age_limit}" ]
			then
				# We cannot do date arithmetic, just signal the "overflow" of the limit.
				last_run="$(tail -n 1 scc.${sys}.log | sed -e 's/:.*//')"
				if [ ! -f ${TMP_SIGNAL} ]
				then
					echo "# Check max. age of ${sys_label}: ${max_age} day(s)"
					touch ${TMP_SIGNAL}
				fi
				echo "CheckSnapAge ${sys} last run on ${last_run}, limit is ${max_age} days"
			fi
		else
			# Empty logfile ?!? Ignore
			:
		fi
	done
	rm -f ${TMP_SIGNAL}

	return 0;
}

scc_check_change()
{
	# Arguments:
	# 1: <systems>
	#	comma separated list of systems or "all_systems"
	# 2: <pattern>
	#	optional, no pattern means: signal any change
	#	regular expression to check in log files or file (in custom) with classifications
	#
	# Output:
	# CheckLogEntries <system> changes for run on <last_date>

	if [ "${1}" != "all_systems" ]
	then
		check_sys="$(echo ${1} | sed -e 's/,/ /g')"
	else
		check_sys="${all_sys}"
	fi

	rm -f ${TMP_SIGNAL}

	for sys in ${check_sys}
	do
		last_line="$(tail -n 3 scc.${sys}.log 2>/dev/null | grep ":result::identical")"
		if [ -n "${last_line}" ]
		then
			continue		# No changes, check next system
		fi

		# Retrieve the date of the last run
		last_date="$(tail -n 1 scc.${sys}.log 2>/dev/null | awk -F: '{ print $1 ":" $2 }')"
		if [ -z "${last_date}" ]
		then
			continue		# Empty/absent log file???
		fi

		if [ -n "${2}" ]
		then
			# Retrieve the changes of the last run.
			grep "^${last_date}:data::" scc.${sys}.log >${TMP_FILE}
			if [ -f "${rules_conf%/*}/${2}" -o -h "${rules_conf%/*}/${2}" ]
			then
				cnt="$(grep -c -f "${rules_conf%/*}/${2}" ${TMP_FILE} 2>/dev/null)"
			else
				cnt="$(grep -c "${2}" ${TMP_FILE} 2>/dev/null)"
			fi
			if [ $? -eq 1 ]
			then
				continue	# No changes that match our criteria
			fi
		else
			# No specific changes, count all. 
			cnt="$(grep -c "^${last_date}:data::" scc.${sys}.log)"
		fi

		if [ ! -f ${TMP_SIGNAL} ]
		then
			if [ -n "${2}" ]
			then
				echo "# Signal specific changes (${2}) in last run of ${sys_label}"
			else
				echo "# Signal any changes in last run of ${sys_label}"
			fi
			touch ${TMP_SIGNAL}
		fi

		echo "CheckLogEntries ${sys} ${cnt} changes for run on $(echo "${last_date}" | sed -e 's/:/ at /')"
	done
	rm -f ${TMP_FILE}
	rm -f ${TMP_SIGNAL}

	return 0;
}

scc_check_identical_cfg()
{
	# Note: use with care as this can be very time-consuming!
	#
	# Arguments:
	# 1: <systems>
	#	comma separated list of systems
	#		first system in list is golden, compare with others
	#	single system
	#		system is golden, compare all other systems
	#	"all_systems"
	#		first (alphabeticsl) system is golden, compare with others
	# 2: <classifications>
	#	literal text of classification or file (in custom) with classifications
	#
	# Output:
	# CompareSnapData <system> <text>

	if [ -f "${rules_conf%/*}/${2}" -o -h "${rules_conf%/*}/${2}" ]
	then
		class_file="${rules_conf%/*}/${2}"
	else
		echo "${2}" >${TMP_FILE}
		class_file=${TMP_FILE}
	fi

	multiple="$(echo "${1}" | grep ",")"
	if [ -n "${multiple}" ]
	then
		# The first system in the list is the golden system.
		golden=${1%%,*}
	else
		golden="${1}"
		check_sys="${all_sys}"
		sys_label="all systems"
	fi

	rm -f ${TMP_SIGNAL}

	# Compare the other systems in the list with the golden system and report differences.
	for sys in ${check_sys}
	do
		if [ "${golden}" != "${sys}" ]
		then

			${SCC_BIN}/scc-syscmp "${class_file}" scc."${golden}".cur scc."${sys}".cur 2>/dev/null	|
			sed -e "s/^/CompareSnapData ${sys} /" >${TMP_DIFF}

			if [ -s ${TMP_DIFF} ]
			then
				if [ ! -f ${TMP_SIGNAL} ]
				then
					echo "# Check ${sys_label} (${golden} is leading) for differences in configuration: ${2}"
					touch ${TMP_SIGNAL}
				fi

				cat ${TMP_DIFF}
			fi
		fi
	done

	rm -f ${TMP_SIGNAL}

	return 0;
}

scc_check_txt()
{
	# Arguments:
	# 1: <systems>
	#	comma separated list of systems or "all_systems"
	# 2: <classifications>
	#	literal text to search for
	#
	# Output:
	# CheckSnapData <system> <found text>

	# Search for the classifications.
	# Ignore the specification of the patterns we are searching.
	for sys in ${check_sys}
	do
		echo "scc.${sys}.cur"
	done					|
	xargs grep "${2}" /dev/null		|
	grep -v ":fix:software:scc-srv:.*::scc-rules.conf:.*CheckSnapData:" >${TMP_FILE}

	if [ -s ${TMP_FILE} ]
	then
		echo "# Check snapshots of ${sys_label} for occurences of \"${2}\""

		sed	-e 's/^scc./CheckSnapData /'	\
			-e 's/\.cur:/ /' ${TMP_FILE}
	fi
	rm -f ${TMP_FILE}

	return 0;
}

scc_format_html()
{
	cat <<-EndOfTxt
		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
		<HTML>
			<HEAD>
				<LINK HREF="${web_realm_dir}/custom/style.css" REL="stylesheet" TYPE="text/css">
				<TITLE>${html_title}</TITLE>
			</HEAD>
			<BODY>
				<A NAME=cfg_top></A>
				<CENTER>
					<A HREF="${scc_web_path_dir}/scc-help/scc-rules-index.html">
					<IMG SRC="${web_realm_dir}/custom/scc-logo.png" ALT=HelpInfo BORDER=0>
					</A>

				</CENTER>
				<H4>
					${HOME_URL}
					${CONFIG_URL}
					${html_hdr}
				</H4>

	EndOfTxt

	echo "<P>The last run of scc-rules produced the following messages:</P>"

	sed	-e 's/&/\&amp;/g'	\
		-e 's/</\&lt;/g'	\
		-e 's/>/\&gt;/g'	\
		-e "s/'/\&#39;/g"	\
		-e 's/"/\&quot;/g'			|
	awk	'BEGIN	{
				c[ 0 ] = "Even";
				c[ 1 ] = "Odd";
				tr_c = 2;		# TR class 
			}
		/^#/			{
						$1 = "";

						if ( NR > 1 )
						{
							if ( length( prev_url ) > 0 )
							{
								print "		</TD>";
								print "	</TR>";
							}
							print "</TBODY>";
							print "</TABLE>";
						}

						print "<H4>" $0 "</H4>";
						print "<TABLE CLASS=SCC BORDER SUMMARY=\"Rules\" CELLSPACING=1 CELLPADDING=4>";
						print "<THEAD>";
						print "	<TR class=Odd align=left>";
						print "		<TH>System</TH>";
						print "		<TH>Message</TH>"
						print "	</TR>";
						print "</THEAD>";

						print "<TBODY>";

						prev_url = "";
						next;
					}
		/^CheckSnapAge/		{
						url = sprintf( "<A HREF=\"%s/scc.%s.html\">%s</A>", d, $2, $2 );
					}
		/^CheckLogEntries/	{
						url = sprintf( "<A HREF=\"%s/scc.%s.log.html#%s_%s\">%s</A>",\
								d, $2, $(NF - 2), $NF, $2 );
					}
		/^CompareSnapData/	{
						url = sprintf( "<A HREF=\"%s/scc.%s.html\">%s</A>", d, $2, $2 );
						compare = 1;
					}
		/^CheckSnapData/	{
						url = sprintf( "<A HREF=\"%s/scc.%s.html\">%s</A>", d, $2, $2 );
					}
			{
				any_data = 1;

				sys = $2;
				$1 = ""; $2 = "";	# erase rule-id and system-name
				sub( "^ *", "", $0 );

				if ( url != prev_url )
				{
					if ( length( prev_url ) > 0 )
					{
						# Terminate previous row.
						print "		</TD>";
						print "	</TR>";
					}
					prev_url = url;

					printf( "<TR class=%s align=left>\n", c[ tr_c++ % 2 ] );
					printf( "	<TD class=Even nowrap valign=top>%s</TD>\n", url );
					print "<TD class=Odd nowrap>"
				}
				else
				{
					print "<BR>";
				}

				b_f = ""; e_f = "";
				if ( compare )
				{
					msg_sys = $1;
					sub( " *:", "", msg_sys );
					if ( msg_sys == sys )
					{
						b_f = "<FONT color=blue>";
					}
					else
					{
						b_f = "<FONT color=red>";
					}
					e_f = "</FONT>";
				}
				printf( "	<TT>%s%s%s</TT>\n", b_f, $0, e_f );
				compare = 0;
			}
		END	{
				if ( any_data )
				{
					print "		</TD>";
					print "	</TR>";
					print "</TBODY>";
					print "</TABLE>";
				}
			}' d="${web_realm_dir}"

}

now=$(date '+%Y-%m-%d %H:%M:%S')
SYNTAX="Syntax error, use: ${ProgName} [ -d <scc_web_path> ] [ -f <rule> ] [ -h <host> ] <realm>"

scc_web_path_dir=""
single_system=""
rules_conf="custom/scc-rules.conf"
while [ $# -gt 1 ]
do
	if [ $# -eq 1 ]
	then
		echo "${ProgName}: Syntax error, missing argument for option: $1" 
		exit 2
	fi

	case "${1}" in
	-d)	if [ "${2}" != "/" ]
		then
			scc_web_path_dir="$2"
		fi
		shift 2
		;;

	-f)	rules_conf="${2}"
		shift 2
		;;

	-h)	cat >${TMP_FILE}
		single_system="${2}"
		shift 2
		;;

	*)	echo "${SYNTAX}" >&2
		exit 1
		;;
	esac
done

if [ $# -ne 1 ]
then
	echo "${SYNTAX}" >&2
	exit 1
fi

if [ -z "${scc_web_path_dir}" ]
then
	scc_web_path_dir="$(sed -n 's/^SCC_WEB_PATH=//p' ${SCC_CONF}/scc.conf 2>/dev/null)"
fi

html_title="Messages for realm ${1}"
html_hdr="Check rules for realm ${1}, last update on: ${now}"

realm="${1}"
web_realm_dir="${scc_web_path_dir}/${realm}"
HOME_URL="<A HREF=\"${web_realm_dir}/index.html\">Home</A>&nbsp;&nbsp;&nbsp;&nbsp;"
CONFIG_URL="<A HREF=\"#cfg\">Configuration</A>&nbsp;&nbsp;&nbsp;&nbsp;"

if [ "${single_system}" ]
then
	html_title="Messages for system ${single_system}"
	html_hdr="Check rules for system ${single_system} in realm ${realm}"
	CONFIG_URL=""
	scc_format_html <${TMP_FILE}
	if [ ! -s ${TMP_FILE} ]
	then
		echo "<P>No messages.</P>"
	fi
	exit 0
fi

if [ ! -d "${SCC_WWW}/${realm}" ]
then
	echo "${ProgName}: directory ${SCC_WWW}/${realm} does not exist" >&2
	exit 1
fi
cd "${SCC_WWW}/${realm}"

# Use the owner/group/permissions of the destination directory for the files we put there.
# We are only interested in the read-permissions of the directory.
set dummy $(ls -ld .)
case "${2}" in
?r??r??r??)	new_mode=0444; umask 333;;
?r??r??-??)	new_mode=0440; umask 337;;
?r??-??-??)	new_mode=0400; umask 377;;
?r??-??r??)	new_mode=0404; umask 373;;
*)		new_mode=0400; umask 077;;
esac
new_owner_group="${4}:${5}"

all_sys="$(find . -name "scc.*.cur" \( -type f -o -type l \)	|
		sed -e 's@^\./@@'				|
		grep -v "/"					|
		sed	-e 's/^scc\.//'		\
			-e 's/\.cur//'				|
		sort)"

# Check the rules.
if [ -f "${rules_conf}" -o -h "${rules_conf}" ]
then
	sed	-e 's/:/ /'		\
		-e 's/:/ /'		\
		-e '/^[ 	]*$/d'	\
		-e '/^[ 	]*#/d' "${rules_conf}" 2>/dev/null	|
	while read check systems parm
	do
		if [ "${systems}" != "all_systems" ]
		then
			check_sys="$(echo ${systems} | sed -e 's/,/ /g')"
			for sys in ${check_sys}
			do
				if [ ! -f scc.${sys}.cur ]
				then
					echo "${ProgName}: ${realm} check ${check}: unkown system: ${sys}" >&2
				fi
			done
			sys_label="specific systems"
		else
			check_sys="${all_sys}"
			sys_label="all systems"
		fi

		case "${check}" in
		CheckSnapAge)		scc_check_max_snap_age "${systems}" "${parm}" 
					;;
		CheckLogEntries)	scc_check_change "${systems}" "${parm}" 
					;;
		CompareSnapData)	scc_check_identical_cfg "${systems}" "${parm}"
					;;
		CheckSnapData)		scc_check_txt "${systems}" "${parm}"
					;;
		SIGNAL*)		:	# Ignore signalling directions.
					;;
		*)			echo "${ProgName}: unknown check: ${check}" >&2
					;;
		esac
	done >scc-rules.data
else
	rm -f scc-rules.data
fi

# Signal the output.
if [ -s scc-rules.data ]
then
	s_prog="$(sed -n -e 's/^SIGNAL_PROG=//p' <"${rules_conf}")"
	s_dest="$(sed -n -e 's/^SIGNAL_DEST=//p' <"${rules_conf}")"
	case "X${s_prog}" in
	Xmail)		(
				echo "Results from scc-rules check for realm: ${realm}"
				echo ""
				cat scc-rules.data
			)		|
			mail "${s_dest}"
			;;
	Xopc)		cnt=$(wc -l <scc-rules.data)
			/opt/OV/bin/OpC/opcmsg	a="SCC"		\
						o="scc-rules"	\
						msg_g="OS"	\
						s="major"	\
						msg_t="scc-rules: reports in ${realm} ${cnt} lines"
			;;
	Xcat)		case "X${s_dest}" in
			Xstdout)	cat scc-rules.data
					;;
			Xnull)		:
					;;
			*)		cat scc-rules.data >"$(basename "${s_dest}")"
					;;
			esac
			;;
	X*)		:		# default: ignore!
			;;
	esac
fi

data_file=/dev/null
if [ -s scc-rules.data ]
then
	data_file=scc-rules.data
fi
scc_format_html <${data_file} >scc-rules-index.html
if [ ! -s scc-rules.data ]
then
	if [ -f "${rules_conf}" -o  -h "${rules_conf}" ]
	then
		echo "<P>The last run of scc-rules produced NO messages</P>"
	else
		echo "<P>There is no file custom/scc-rules.conf to check for rules.</P>"
	fi
fi >>scc-rules-index.html

# Add the configuration to the output.
if [ -f "${rules_conf}" -o  -h "${rules_conf}" ]
then
	echo "<HR>"
	echo "<A NAME=\"cfg\"></A>"
	echo "<H4><A HREF=\"#cfg_top\">Top</A>&nbsp;&nbsp;&nbsp;&nbsp;Configuration files</H4>"
	echo "<P>The program scc-rules uses the following configuration files for this realm:</P>"
	sed -e '/^#/d' "${rules_conf}"		|
	awk -F: 'BEGIN	{
				print "scc-rules.conf";
			}
		{
			if ( $3 != "" )
			{
				if ( ! visited[ $3 ] )
				{
					print $3;
					visited[ $3 ] = 1;
				}
			}
		}'				|
	while read file
	do
		if [ -f "${rules_conf%/*}/${file}" -o -h "${rules_conf%/*}/${file}" ]
		then
			echo "<H4>${file}</H4>"
			echo "<PRE>"
			sed	-e '/^#/d'		\
				-e '/^[ 	]*$/d'	\
				-e 's/&/\&amp;/g'	\
				-e 's/</\&lt;/g'	\
				-e 's/>/\&gt;/g'	\
				-e "s/'/\&#39;/g"	\
				-e 's/"/\&quot;/g' "${rules_conf%/*}/${file}"
			echo "</PRE>"
		fi
	done

	echo "	</BODY>"
	echo "</HTML>"
fi >>scc-rules-index.html
chown "${new_owner_group}" scc-rules-index.html scc-rules.data 2>/dev/null

exit 0
