#!/bin/bash
# vim: set fileencoding=utf-8 ts=4 sw=4 noexpandtab

PORTICRON_VERSION="0.7.2"

version() {
	echo "porticron ${PORTICRON_VERSION}"
	echo "Copyright (c) 2008-2009 Benedikt Böhm <bb@xnull.de>"
	exit 0
}

usage() {
	echo "Usage: porticron [-hvn] [-c <file>]"
	echo
	echo "  -h         print this help text"
	echo "  -v         enable verbose output"
	echo "  -V         print version number"
	echo "  -n         do not send upgrade mails"
	echo "  -c <file>  use configuration in <file>"
	echo
	exit 0
}

log() {
	[[ ${VERBOSE} -eq 1 ]] && echo "$@" >&2
}

send_mail() {
	if [[ ${NOMAIL} -eq 1 ]]; then
		if [[ ${VERBOSE} -eq 1 ]]; then
			cat
		else
			cat > /dev/null
		fi
	else
		${SENDMAIL:-/usr/sbin/sendmail} -t
	fi
}

mkhash() {
	echo "${1}" | md5sum | cut -f1 -d' '
}

save_msg() {
	HASH_FILE="${TMP}${1}"
	log "creating hash file ${HASH_FILE}"
	mkhash "${2}" > "${HASH_FILE}"
}

# Test if msg with id $1 and body $2 is equal to previous saved msg
# Returns: 1 - if msg is equal, 0 - if msg is different, unknown, etc
check_msg() {
	if [[ ${CHECK_DUP_MSG} -eq 0 ]]; then
		return 0
	fi
	if [[ ${NOMAIL} -eq 1 ]]; then
		return 0
	fi

	HASH_FILE="${TMP}${1}"

	if [ ! -f "${HASH_FILE}" ]; then
		log "no previous hash file ${HASH_FILE} exists"
		save_msg "$1" "$2"
		return 0
	fi

	OLD_HASH=$(cat "${HASH_FILE}")
	NEW_HASH=$(mkhash "${2}")
	if [[ "${OLD_HASH}" == "${NEW_HASH}" ]]; then
		log "hash matched for hash file ${HASH_FILE}"
		return 1
	else
		save_msg "$1" "$2"
		log "hash unmatched for hash file ${HASH_FILE}"
		return 0
	fi
}


# parse command line
while getopts "hvVnc:" opt; do
	case $opt in
		h) usage;;
		v) VERBOSE=1;;
		V) version;;
		n) NOMAIL=1;;
		c) PORTICRON_CONF=${OPTARG};;
		?) exit 1;;
	esac
done


# defaults, more below
: ${VERBOSE:=0}
: ${NOMAIL:=0}
: ${PORTICRON_CONF:=/etc/porticron.conf}
: ${TMP:="/var/tmp/porticron."}
: ${CHECK_DUP_MSG:=1}
: ${SUBJECT:=Gentoo package updates on {FQDN\} [ {IP\} ]}
: ${SUBJECT_WARN:=WARNING: Gentoo security updates on {FQDN\} [ {IP\} ]}
: ${EMERGE:=/usr/bin/emerge}
log "using PORTICRON_CONF=${PORTICRON_CONF}, NOMAIL=${NOMAIL}, VERBOSE=${VERBOSE}"


# load config
if [[ ! -r ${PORTICRON_CONF} ]]; then
	echo "could not open configuration file ${PORTICRON_CONF}"
	exit 1
else
	source ${PORTICRON_CONF}
fi


# detect some common variables
SCRIPT_NAME=$(basename $0)
: ${FQDN:=$(hostname --fqdn)}
: ${HOST:=$(hostname -s)}
IP=$(getent hosts ${FQDN} | cut -d ' ' -f 1)
: ${DATE:=$(date -R)}
: ${PORTDIR:=$(portageq get_repo_path $(portageq envvar EROOT) gentoo)}

# defaults, second half
: ${RCPT:=root}
: ${FROM:=root@${FQDN}}
: ${SYNC_CMD:=${EMERGE} --sync}
: ${SYNC_OVERLAYS_CMD:=/bin/true}
: ${GLSA_CHECK:=/usr/bin/glsa-check}

# replace variables
for var in FQDN HOST IP DATE; do
    SUBJECT=${SUBJECT/\{$var\}/${!var}}
    SUBJECT_WARN=${SUBJECT_WARN/\{$var\}/${!var}}
done

# sync if desired
log "running SYNC_CMD: ${SYNC_CMD}"
${SYNC_CMD} &>/dev/null
log "running SYNC_OVERLAY_CMD: ${SYNC_OVERLAYS_CMD}"
${SYNC_OVERLAYS_CMD} &>/dev/null


# run emerge once to perform global updates while not cluttering mail output
${EMERGE} --info &>/dev/null


# GLSA check
log "running GLSA_AFFECTED: ${GLSA_CHECK} --test --nocolor --verbose affected"
GLSA_AFFECTED=$(${GLSA_CHECK} --test --nocolor --verbose affected 2>/dev/null)
log "running GLSA_UPGRADES: ${GLSA_CHECK} --nocolor --pretend affected"
GLSA_UPGRADES=$(${GLSA_CHECK} --nocolor --pretend affected | grep '^     ')

if [[ -n ${GLSA_AFFECTED} ]]; then
	if check_msg GLSA_AFFECTED "${GLSA_AFFECTED}"; then
		GLSA_MSG="
${SCRIPT_NAME} has detected that this system is affected by the following GLSAs:

$(echo "${GLSA_AFFECTED}" | sed 's/^20/     20/')

========================================================================

The following updates should be performed for these GLSAs:

${GLSA_UPGRADES}
"
		cat <<EOF | send_mail
To: ${RCPT}
From: ${FROM}
Subject: ${SUBJECT_WARN}
Date: ${DATE}

porticron report [${DATE}]
========================================================================
${GLSA_MSG}
-- 
${SCRIPT_NAME}
EOF
	fi
fi

# build a list of changed ebuilds
if [[ -n ${DIFF_CMD} ]]; then
	log "running DIFF_CMD: ${DIFF_CMD}"
	DIFF=$(${DIFF_CMD} 2>/dev/null)
fi

if [[ -n ${DIFF} ]]; then
	DIFF_MSG="${SCRIPT_NAME} has detected the following changes to ${PORTDIR}:

${DIFF}

========================================================================
"
fi


# build list of upgrades
: ${UPGRADE_OPTS:=--deep --update}
log "running UPGRADE_CMD: ${EMERGE} ${UPGRADE_OPTS} --quiet --pretend world"
UPGRADE=$(${EMERGE} ${UPGRADE_OPTS} --quiet --pretend world 2>/dev/null)

if [[ -n ${UPGRADE} ]]; then
	UPGRADE_MSG="
${SCRIPT_NAME} has detected that some packages need upgrading:

$(echo "${UPGRADE}" | sed 's/^\[/    [/')

========================================================================

You can perform the upgrade by issuing the command:

    emerge ${UPGRADE_OPTS} world

as root on ${FQDN}

It is recommended that you pretend the upgrade first to confirm that
the actions that would be taken are reasonable. The upgrade may be
pretended by issuing the command:

    emerge ${UPGRADE_OPTS} --pretend world

"
fi


# send mail
if [[ -z ${UPGRADE_MSG} && -z ${DIFF_MSG} ]]; then
	log "no upgrades found, exiting."
	exit 0
fi

# We need to execute both check_msg actually to save hash files
check_msg UPGRADE_MSG "${UPGRADE_MSG}"
UPGRADE_MSG_CODE=$?
# TODO: Output of DIFF_MSG can contain dates and status of currently installed packages.
# This will trigger e-mail sending even if e.g. eix cache is not changed.
check_msg DIFF_MSG "${DIFF_MSG}"
DIFF_MSG_CODE=$?

if [[ ${UPGRADE_MSG_CODE} -eq 1 && ${DIFF_MSG_CODE} -eq 1 ]]; then
	log "no new upgrades found, exiting."
	exit 0
fi

cat <<EOF | send_mail
To: ${RCPT}
From: ${FROM}
Subject: ${SUBJECT}
Date: ${DATE}

porticron report [${DATE}]
========================================================================
${DIFF_MSG}${UPGRADE_MSG}
-- 
${SCRIPT_NAME}
EOF
