#!/bin/bash

SVER='1.0.21'

##############################################################################
#  sdagent-patterns - SCA Agent Pattern Updater
#  Copyright (C) 2014-2018 SUSE LLC
#
# Description:  Maintains a current list of patterns from the public pattern
#               repository.
# Runs on:      Agent Server
# Modified:     2018 Jan 03
#
##############################################################################
#
#  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; version 2 of the License.
#
#  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, see <http://www.gnu.org/licenses/>.
#
#  Authors/Contributors:
#     Jason Record (jason.record@suse.com)
#
##############################################################################

CURRENT_SCRIPT=$(basename $0)
. /etc/sca/sdagent.conf
[[ -s /etc/sca/sdagent-patterns.conf ]] && . /etc/sca/sdagent-patterns.conf
PAT_COUNT_PRE=0
PAT_COUNT_POST=0
PAT_COUNT_CHANGE=0
PAT_COUNT=0
UPDATE_COUNT_NEW=0
UPDATE_COUNT_DEL=0
UPDATE_COUNT_MOD=0
MSG_PAT_HTML=$(mktemp ${SCA_WORK}/_update_msg_pat_html_${CURRENT_SCRIPT}.XXXXXXXXXX)
MSG_PAT_TEXT=$(mktemp ${SCA_WORK}/_update_msg_pat_text_${CURRENT_SCRIPT}.XXXXXXXXXX)
SVN_FILE="${SCA_WORK}/svn-change-list.txt"
PATTERNS="${SCA_WORK}/manifest.patterns"

title() {
	test $LOGLEVEL -ge $LOGLEVEL_NORMAL && clear
	msg normal "SCA_PATTERNS" "$CURRENT_SCRIPT v$SVER"
}

showHelp() {
	[[ -n "$1" ]] && { echo "$1"; echo; }
	echo "Usage: $CURRENT_SCRIPT [OPTIONS]"
	echo "Description:"
	echo "  Updates patterns from the OBS repository and checks the"
	echo "  updates into the SCA appliance database."
	echo
	echo "Options:"
	echo "  -h  This screen"
	echo "  -u  Do not update patterns from the update repository, "
	echo "      just check in current patterns."
	echo
}

msg() {
	FACLEVEL='info'
	case $1 in
	debug) SYSTEM_LOGGER=0; CURRENT_LOGLEVEL=$LOGLEVEL_DEBUG ;;
	verbose) SYSTEM_LOGGER=1; CURRENT_LOGLEVEL=$LOGLEVEL_VERBOSE ;;
	normal) SYSTEM_LOGGER=1; CURRENT_LOGLEVEL=$LOGLEVEL_NORMAL ;;
	min*) SYSTEM_LOGGER=1; CURRENT_LOGLEVEL=$LOGLEVEL_MIN ;;
	silent) SYSTEM_LOGGER=1; CURRENT_LOGLEVEL=$LOGLEVEL_SILENT ;;
	*) SYSTEM_LOGGER=1; CURRENT_LOGLEVEL=$LOGLEVEL_MIN ;;
	esac
	shift
	if [[ $LOGLEVEL -ge $CURRENT_LOGLEVEL ]]; then
		if [[ $SYSTEM_LOGGER -gt 0 ]]; then
			[[ -n "$2" ]] && logger -p "user.${FACLEVEL}" "${CURRENT_SCRIPT}[$$]: [$1] $2" || logger -p "user.${FACLEVEL}" "${CURRENT_SCRIPT}[$$]: $1"
			if [[ $LOGLEVEL -gt $LOGLEVEL_SILENT ]]; then
				printf "%-15s %s\n" "$1" "$2"
			fi
		else
			printf "%-15s %s\n" "$1" "$2"
		fi
	fi
}


notifyAdmin() {
	if [[ -x /usr/bin/mailx ]]; then
		EVENT_STR=$1
		shift
		for SENDTO in $EMAIL_ADMIN
		do
			echo "$*" | /usr/bin/mailx -r "SCA Agent Notification <root>" -ns "Pattern Update: $EVENT_STR" $SENDTO
		done
	fi
}

agentAccess() {
	# returns 0 NO_ACCESS, 1 INACTIVE, 2 ACTIVE
	CHECK_AGENT_STATE=$(mysql -NB $DB_CONNECT -e "SELECT AgentState FROM Agents WHERE AgentID=$AGENT_ID")
	RCODE=$?
	if [[ $RCODE -gt 0 ]]; then
		msg normal DB "ERROR Cannot access administrative database $DB_NAME on localhost"
		RCODE=$AGENT_NOACCESS
	else
		case $CHECK_AGENT_STATE in
		Active) RCODE=$AGENT_ACTIVE ;;
		Error) RCODE=$AGENT_NOACCESS ;;
		*) RCODE=$AGENT_INACTIVE ;;
		esac
		msg verbose ACCESS "Agent State '$CHECK_AGENT_STATE', setting agentAccess = $RCODE"
	fi
	return $RCODE
}

repoRefreshed() {
	msg normal PATTERNS "Refreshing SCA Pattern Repository"
	zypper --no-gpg-checks --non-interactive refresh &>/dev/null
	RC=$?
	if (( RC )); then
		msg min ERROR "Cannot refresh pattern repository, zypper error $RC"
		[[ $EMAIL_LEVEL -ge $EMAIL_MIN ]] && notifyAdmin 'Agent $AGENT_ID cannot refresh pattern repository' "ERROR: Failed to refresh pattern repository on AgentID $AGENT_ID"
	fi
	return $RC
}

updatePatterns() {
	msg normal PATTERNS "Updating SCA Patterns"
	zypper --no-gpg-checks --non-interactive update sca-patterns-* &>/dev/null
	RC=$?
	if (( RC )); then
		msg min ERROR "Failed to update SCA patterns, zypper error $RC"
		[[ $EMAIL_LEVEL -ge $EMAIL_MIN ]] && notifyAdmin 'Agent $AGENT_ID cannot update patterns from pattern repository' "ERROR: Failed to update patterns from the pattern repository on AgentID $AGENT_ID"
	fi	
	return $RC
}

getPackageFiles() {
	TYPE=$1
	for PKG in $PKG_LIST
	do
		FILE="${SCA_WORK}/_update_${TYPE}_${PKG}_${CURRENT_SCRIPT}"
		rpm -q --dump $PKG | grep -v '0000000000' | awk '{print $1,$4}' | sort  > $FILE 2>/dev/null
	done
}

getPackageStats() {
	for PKG in $PKG_LIST
	do
		FILE="${SCA_WORK}/_update_diff_${PKG}_${CURRENT_SCRIPT}"
		diff -u0 ${SCA_WORK}/_update_before_${PKG}_${CURRENT_SCRIPT} ${SCA_WORK}/_update_after_${PKG}_${CURRENT_SCRIPT} | sed -e '1,2d' | grep -v '^@' > $FILE
		NEW_LIST=$(grep '^+' $FILE | sed -e 's/^\+//' | awk '{print $1}')
		REMOVED_LIST=$(grep '^-' $FILE | sed -e 's/^-//' | awk '{print $1}')
		NEW_CNT=$(grep '^+' $FILE | wc -l)
		REMOVED_CNT=$(grep '^-' $FILE | wc -l)
		MODIFIED_CNT=0
		for PAT in $REMOVED_LIST
		do
			if echo $NEW_LIST | grep $PAT &>/dev/null
			then
				((MODIFIED_CNT++))
				((NEW_CNT--))
				((REMOVED_CNT--))
			fi
		done
		UPDATE_COUNT_NEW=$(( UPDATE_COUNT_NEW + NEW_CNT ))
		UPDATE_COUNT_DEL=$(( UPDATE_COUNT_DEL + REMOVED_CNT ))
		UPDATE_COUNT_MOD=$(( UPDATE_COUNT_MOD + MODIFIED_CNT ))
		cat << MSG_HTML_EOF >> $MSG_PAT_HTML
<tr><td align="left">$PKG</td><td align="center">$NEW_CNT</td><td align="center">$REMOVED_CNT</td><td align="center">$MODIFIED_CNT</td></tr>
MSG_HTML_EOF
		cat << MSG_TEXT_EOF >> $MSG_PAT_TEXT
${PKG}: New, $NEW_CNT; Removed: $REMOVED_CNT; Modified: $MODIFIED_CNT
MSG_TEXT_EOF
	done
}

sendEmailUpdate() {
	BOUNDARY="========"$(date +%Y%m%d%N)"=="
	SUBJECT="Pattern Update Report, Total Patterns: $PAT_COUNT; New: ${UPDATE_COUNT_NEW}, Removed: ${UPDATE_COUNT_DEL}, Modified: ${UPDATE_COUNT_MOD}"
	for EMAILTO in $EMAIL_REPORT
	do
		MSG_BODY=$(mktemp ${SCA_WORK}/_update_msg_body_${CURRENT_SCRIPT}.XXXXXXXXXXXXXXX)
		cat << MEOF1 > $MSG_BODY
From: SCA Agent Notification <root>
To: $EMAILTO
Subject: $SUBJECT
Content-Type: multipart/alternative; boundary="$BOUNDARY"
MIME-Version: 1.0

--${BOUNDARY}
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

* Update Summary *
Total Active Patterns:  $PAT_COUNT
  New: $UPDATE_COUNT_NEW
  Removed: $UPDATE_COUNT_DEL
  Modified: $UPDATE_COUNT_MOD
Local Agent Patterns:  $PAT_COUNT_LOCAL
Pattern Modules:  $MODULES
Agent ID:  $AGENT_ID


* Package Details *
MEOF1

cat $MSG_PAT_TEXT >> $MSG_BODY

cat << MEOF2 >> $MSG_BODY

--${BOUNDARY}
Content-Type: text/html; charset="ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<h1>Update Summary</h1>
<table>
<tr><td>Total Active Patterns:</td><td>$PAT_COUNT</td></tr>
<tr><td>&nbsp;&nbsp;New:</td><td>$UPDATE_COUNT_NEW</td></tr>
<tr><td>&nbsp;&nbsp;Removed:</td><td>$UPDATE_COUNT_DEL</td></tr>
<tr><td>&nbsp;&nbsp;Modified:</td><td>$UPDATE_COUNT_MOD</td></tr>
<tr><td>Local Agent Patterns:</td><td>$PAT_COUNT_LOCAL</td></tr>
<tr><td>Agent ID:</td><td>$AGENT_ID</td></tr>
<tr><td>Pattern Modules:</td><td>$MODULES</td></tr>
</table>
<br />
<h2>Package Details</h2>
<table cellpadding="2">
<tr><td align="left"><b>Package<b></td><td align="center"><b>New</b></td><td align="center"><b>Removed</b></td><td align="center"><b>Modified</b></td></tr>
MEOF2

cat $MSG_PAT_HTML >> $MSG_BODY
cat << MEOF3 >> $MSG_BODY
</table>
</body></html>

--${BOUNDARY}--
MEOF3
			cat $MSG_BODY | /usr/sbin/sendmail -t
			rm -f $MSG_BODY
	done
}

while getopts ':hu' TMPOPT
do
	case $TMPOPT in
		\:) title; showHelp "ERROR: Missing Argument -$OPTARG"; exit 1 ;; 
		\?) title; showHelp "ERROR: Invalid Option -$OPTARG"; exit 2 ;;
		h) title; showHelp; exit 0 ;;
		u) UPDATE_FROM_PATTERN_REPO=0 ;;
	esac
done

PAT_COUNT_LOCAL=$(find ${SCA_PATTERN_PATH}/local -type f -perm /100 | sort | uniq | wc -l)
if (( UPDATE_FROM_PATTERN_REPO )); then
	PAT_COUNT_PRE=$(find $SCA_PATTERN_PATH -type f -perm /100 | sort | uniq | wc -l)
	if repoRefreshed; then
		PKG_LIST=$(rpm -qa --qf "%{NAME}\n" | grep 'sca-patterns-' | sort)
		getPackageFiles before
		updatePatterns
		getPackageFiles after
		getPackageStats
	fi
	PAT_COUNT_POST=$(find $SCA_PATTERN_PATH -type f -perm /100 | sort | uniq | wc -l)
	PAT_COUNT_CHANGE=$(( UPDATE_COUNT_NEW + UPDATE_COUNT_DEL + UPDATE_COUNT_MOD ))
else
	PAT_COUNT_POST=$(find $SCA_PATTERN_PATH -type f -perm /100 | sort | uniq | wc -l)
fi
PAT_COUNT=$PAT_COUNT_POST
agentAccess
AGENT_STATE=$?
if [[ $AGENT_STATE -eq $AGENT_NOACCESS ]]; then
	msg normal DB "Error: Insufficient database access, AGENT_STATE=$AGENT_STATE"
	[[ $EMAIL_LEVEL -ge $EMAIL_MIN ]] && notifyAdmin "Agent Access" "ERROR Insufficient database access, AGENT_STATE=$AGENT_STATE"
	exit 5
else
	if [[ -d ${SCA_PATTERN_PATH} ]]; then
		msg normal "v$SVER" "Pattern Pre-Configuration"
		chmod 700 ${SCA_PATTERN_PATH}
		if [[ -n "$AGENT_ID" ]]; then
			if (( PAT_COUNT > 0 )); then
				RPM_MODS=$(rpm -qa | grep sca-patterns- | cut -d- -f3 | sort)
				LOCAL_COUNT=$(find ${SCA_PATTERN_PATH}/local -type f -perm /100 | sort | uniq | wc -l)
				if (( $LOCAL_COUNT > 0 )); then
					MODULES="$(echo $RPM_MODS) local"
				else
					MODULES=$(echo $RPM_MODS)
				fi
				AGENT_MSG="$PAT_COUNT with $PAT_COUNT_CHANGE changes, Modules: $MODULES"
				msg normal STATUS "$AGENT_MSG"
				mysql $DB_CONNECT -e "UPDATE Agents SET AgentEvent=NOW(), AgentMessage='$AGENT_MSG', Patterns=$PAT_COUNT WHERE AgentID=$AGENT_ID"
				if [[ $? -gt 0 ]]; then
					msg min ERROR "Failed to update AgentID $AGENT_ID"
					[[ $EMAIL_LEVEL -ge $EMAIL_MIN ]] && notifyAdmin 'Agent $AGENT_ID Update Failed' "ERROR: Failed to update AgentID $AGENT_ID"
					exit 3
				else
					msg min PATTERNS "v$SVER -- $PAT_COUNT patterns checked in on Agent $AGENT_ID, Changes: $PAT_COUNT_CHANGE"
					if (( PAT_COUNT_CHANGE != 0 ))
					then
						[[ $EMAIL_LEVEL -ge $EMAIL_MIN ]] && sendEmailUpdate
					else
						[[ $EMAIL_LEVEL -ge $EMAIL_VERBOSE ]] && sendEmailUpdate
					fi
				fi
			else
				mysql $DB_CONNECT -e "UPDATE Agents SET AgentEvent=NOW(), AgentMessage='Disabling Agent, No patterns found', Patterns=$PAT_COUNT, AgentState='Inactive' WHERE AgentID=$AGENT_ID"
				if [[ $? -gt 0 ]]; then
					msg min ERROR "Failed to update AgentID $AGENT_ID"
					[[ $EMAIL_LEVEL -ge $EMAIL_MIN ]] && notifyAdmin 'Agent $AGENT_ID Update Failed' "ERROR: Failed to update AgentID $AGENT_ID"
					exit 3
				else
					msg min PATTERNS "v$SVER -- $PAT_COUNT patterns on Agent $AGENT_ID"
					if (( PAT_COUNT_CHANGE  ))
					then
						[[ $EMAIL_LEVEL -ge $EMAIL_MIN ]] && sendEmailUpdate
					else
						[[ $EMAIL_LEVEL -ge $EMAIL_VERBOSE ]] && sendEmailUpdate
					fi
				fi
			fi
		else
			msg min CONFIG "Run sdagent-config on $SERVER_NAME"
			[[ $EMAIL_LEVEL -ge $EMAIL_MIN ]] && notifyAdmin 'Run sdagent-config' "Run sdagent-config on $SERVER_NAME"
			exit 4
		fi
	else
		msg min ERROR "Error: Invalid pattern directory: $SCA_PATTERN_PATH"
		[[ $EMAIL_LEVEL -ge $EMAIL_MIN ]] && notifyAdmin 'Invalid pattern directory' "Error: Invalid pattern directory: $SCA_PATTERN_PATH"
		exit 1
	fi
fi
rm -f ${SCA_WORK}/_update_*${CURRENT_SCRIPT}*
exit 0

