#!/bin/sh
# DocumentId: $Id: cron-apt 2462 2007-09-16 18:05:35Z ola $
#
# Copyright (C) 2002-2007 Ola Lundqvist <opal@debian.org>
# Copyright (C) 2004-2007 Marc Haber <mh+debian-bugs@zugschlus.de>
# Copyright (C) 2004,2007 Bob Proulx <bob@proulx.com>
# Copyright (C) 2004      Marc Sherman <msherman@projectile.ca>
# Copyright (C) 2004      David Weinehall
# Copyright (C) 2003      Sean Finney <seanius@seanius.net>
# Copyright (C) 2002      Marcel Kolaja <marcel@solnet.cz>
#
# 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., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
#
# Changes:
#       2007-08-02 Ola Lundqvist <opal@debian.org>
#               Added support for *_HERE variables in the action.d files.
#               They are copied to the * variable before the action loop
#               and then restored in the end of the loop.
#               Marc Haber gave the idea to have variables that are applicable
#               for only the action item.
#               Also moved out the functions so they can be tested separately.
#       2007-08-01 Ola Lundqvist <opal@debian.org>
#               Changed so that the tmp size checking is done later.
#       2007-07-31 Ola Lundqvist <opal@debian.org>
#               Based on a patch from Marc Haber <mh+debian-bugs@zugschlus.de>
#               cron-apt now fail more gracefully if the mail command is not
#               available.
#               Applied patch from Marc Haber <mh+debian-bugs@zugschlus.de> to
#               make a more graceful check if the tmp directory is full.
#               Modified the patch so it is configurable.
#       2007-07-21 Bob Proulx <bob@proulx.com>, Ola Lundqvist <opal@debian.org>
#               Correction of MAILONMSGSDIR and SYSLOGONMSGSDIR.
#       2007-07-06 Ola Lundqvist <opal@debian.org>
#               Check refrain file even after sleep.
#       2006-11-26 Ola Lundqvist <opal@debian.org>
#               Corrected stupid syntax error.
#       2006-11-23 Ola Lundqvist <opal@debian.org>
#               Make sure that initlog is removed on exit.
#       2006-04-30 Marc Haber <mh+debian-bugs@zugschlus.de>
#               Move dotlock code so that now the entire cron-apt run
#               is protected by locking
#	2005-10-09 Marc Haber <mh+debian-bugs@zugschlus.de>
#		Add $APTCOMMAND to the CRON-APT line output.
#	2005-10-07 Ola Lundqvist <opal@debian.org>
#		Changed name for DIFF ignore option.
#	2005-10-07 Marc Haber <mh+debian-bugs@zugschlus.de>
#		Support for DIFF ignore directive and better hostname handling.
#	2005-04-20 Ola Lundqvist <opal@debian.org>
#		Modified refrain file system to make file name configurable.
#	2005-04-20 Marc Haber <mh+debian-bugs@zugschlus.de>
#		Added support for refrain file.
#	2004-12-25 Marc Sherman <msherman@projectile.ca>
#		Added configurable support for aptitude instead of apt-get.
#	2004-09-23 Ola Lundqvist <opal@debian.org>
#		Escaped changes file data so '/' can be used in action.d lines.
#	2004-09-19 Marc Haber <mh+debian-bugs@zugschlus.de>
#		Add warning if dotlockfile not found.
#	2004-09-12 Ola Lundqvist <opal@debian.org>
#		Modified patch from Marc Haber to avoid symlink attacks.
#		Also use nicer cleaning.
#	2004-09-12 Marc Haber <mh+debian-bugs@zugschlus.de>
#		Patch to have multiple cron-apt running at the same time
#		without messing up the changelog.
#	2004-09-07 Marc Haber <mh+debian-bugs@zugschlus.de>
#		Patch to achieve run-parts behaviour.
#	2004-08-13 Ola Lundqvist <opal@debian.org>
#		Bugfixes.
#		Made *ON=changes available to more than only mail.
#	2004-08-12 Ola Lundqvist <opal@debian.org>
#		Added support for mailon=changes.
#		Marc Haber <mh+debian-bugs@zugschlus.de> give me the patch
#		that I have modified.
#		Fixed some other bugs that was introduced by me.
#		Added support for syslogon.
#	2004-08-09 Ola Lundqvist <opal@debian.org>
#		Cleaned up some code and fixed a minor logging bug.
#		Better argument checking code. Can now handle more than
#		one option.
#		Changed from status passing from echo to file.
#		Fixed so that stdout argument really works.
#	2004-08-08 Ola Lundqvist <opal@debian.org>
#		Fixed variable bug.
#		Cleaned up the code.
#		Fixed output function framework so it works as expected.
#		Better option handling and local output option.
#	2004-07-22 Ola Lundqvist <opal@debian.org>
#		Made it more POSIX compliant by using /dev/random if the
#		RANDOM variable do not contain a value. Thanks to
#		Bob Proulx <bob@proulx.com> and David Weinehall for the idea.
#		Also changed -a and -o to && and ||, as Bob suggested.
#	2003-07-10 Ola Lundqvist <opal@debian.org>
#		Always run even if there was an error last time is now the
#		default. Fix so that you can write RUNSLEEP=0 too.
#	2003-07-10 Ola Lundqvist <opal@debian.org>
#		Added runsleep configuration option.
#		Now cron-apt is aware of the DEBIAN_FRONTEND = noninteractive
#		ENVIRONMENT.
#       2003-07-10 Ola Lundqvist <opal@debian.org>
#               Patch for mailon=output option from Sean Finney
#               <seanius@seanius.net>.
#       2003-04-17 Ola Lundqvist <opal@debian.org>
#               Added mailon=output option using patch from Sean Finney
#               <seanius@seanius.net>.
#       2002-08-14 Ola Lundqvist <opal@debian.org>
#               Implemented mail on upgrade only using patches from Marcel Kolaja
#               <marcel@solnet.cz>. Thanks a lot for these patches.
#	2002-04-07 Ola Lundqvist <opal@debian.org>
#		Added skipping of comment lines in action files.
#		Added exra config.d directory.
#		Rewrote the mail and log part to make it more flexible.
#		Christian Perrier <Christian.Perrier@onera.fr> was the
#		one that needed this flexibility.
#		Also added skipping of backup files (*~).
#	2002-03-05 Ola Lundqvist <opal@debian.org>
#		Made it complete so it can be released.
#	2002-03-03 Ola Lundqvist <opal@debian.org>
#		Wrote the beginning.
#	2002-04-24 Ola Lundqvist <opal@debian.org>
#		Added a default path. Thanks to Donovan Baarda
#		<abo@minkirri.apana.org.au> for pointing it out.

export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
UMASK_TIGHT="077"
UMASK_RELAXED="022"
umask $UMASK_TIGHT

############################# arguments #######################################

STDOUT=""
ALLCONFIGS=""

while [ -n "$*" ] ; do
    if [ "$1" = "--help" ] ; then
	echo "USAGE: cron-apt [-i] [-s] [configfiles]"
	exit 0
    elif [ "$1" = "-i" ] ; then
	RUNIMMEDIATELY="yes"
	shift
    elif [ "$1" = "-s" ] ; then
	DEBUG=always
	STDOUT=yes
	shift
    else
	CONFIG="$1"
    	ALLCONFIGS="$ALLCONFIGS $CONFIG"
	shift
    fi
done
if [ -z "$CONFIG" ] ; then
    CONFIG=/etc/cron-apt/config
    ALLCONFIGS="$CONFIG"
fi

############################## lib and tmp dirs ###############################

LIBDIR="/var/lib/cron-apt"
SHAREDIR="/usr/share/cron-apt"
CONFIGDIRNAME="$(echo $CONFIG | sed 's|/|_-_|g')"
TMPDIR=$(mktemp -d -t cron-apt.XXXXXX)
if [ $? -ne 0 ]; then
    echo >&2 "Error: Can not create a safe temporary directory."
    exit 1
fi

INITLOG="$TMPDIR/initlog"
RUNERROR="$TMPDIR/runerror"
RUNSYSLOG="$TMPDIR/runsyslog"
RUNLOG="$TMPDIR/runlog"
RUNMAIL="$TMPDIR/runmail"
ACTIONERROR="$TMPDIR/actionerror"
ACTIONSYSLOG="$TMPDIR/actionsyslog"
ACTIONLOG="$TMPDIR/actionlog"
ACTIONMAIL="$TMPDIR/actionmail"
TEMP="$TMPDIR/temp"
MAIL="$TMPDIR/mail"
DIFF="$TMPDIR/difftemp"
STATUS="$TMPDIR/status"

LOCKFILE="$LIBDIR/lockfile"
MAILCHDIR="$LIBDIR/$CONFIGDIRNAME/mailchanges"
ERROR="$TMPDIR/$CONFIGDIRNAME-error"

############################## defaults #######################################

ACTIONDIR="/etc/cron-apt/action.d"
ACTIONCONFDIR="/etc/cron-apt/config.d"
MAILMSGDIR="/etc/cron-apt/mailmsg.d"
MAILONMSGSDIR="/etc/cron-apt/mailonmsgs"
SYSLOGONMSGSDIR="/etc/cron-apt/syslogonmsgs"
REFRAINFILE="/etc/cron-apt/refrain"
NOLOCKWARN=""
ERRORMSGDIR="/etc/cron-apt/errormsg.d"
SYSLOGMSGDIR="/etc/cron-apt/syslogmsg.d"
LOGMSGDIR="/etc/cron-apt/logmsg.d"
LOG="/var/log/cron-apt/log"
DIFFONCHANGES="prepend"
MAILTO="root"
# error, always, never
SYSLOGON="upgrade"
# error, always
MAILON="error"
# error, never
EXITON="error"
# verbose, error
DEBUG="verbose"
# general options
OPTIONS="-o quiet=1"
# do always run as default
DONTRUN=""
# Random sleep time before run
RUNSLEEP="3600"
# Minimum amount of discspace needed in /tmp
MINTMPDIRSIZE=10
# The command to use (can be aptitude instead)
APTCOMMAND="/usr/bin/apt-get"
# If HOSTNAME is non-empty, the contents will be used to generate the
# e-mail subject for notifications sent out. If HOSTNAME is empty, the
# output of $(uname -n) will be used.
HOSTNAME=""
# Ignore lines matching this regexp to determine whether changes occurred
# for MAILON="changes".
DIFFIGNORE=""
# What to do with the diff when *ON=changes.
# Value: prepend (prepend to the output)
#        append  (append to the output)
#        only    (only show the diff, not the output itself)
#                (else do nothing)
DIFFONCHANGES=prepend

export DEBIAN_FRONTEND="noninteractive"
export LANG="C"
export LC_ALL="C"

# Read configuration.
for cfg in $ALLCONFIGS; do
  if [ -f "$cfg" ] ; then
    . "$cfg"
  else
    echo >&2 "The config file $cfg does not exist."
  fi
done

if [ -t 0 ]; then
    RUNIMMEDIATELY="yes"
fi
if test "$RUNIMMEDIATELY" = "yes"; then
    RUNSLEEP=
fi

############################## functions ######################################

. $SHAREDIR/functions

############################### check #########################################

if ! [ -d "$LIBDIR/$CONFIGDIRNAME" ]; then
    mkdir -p "$LIBDIR/$CONFIGDIRNAME"
fi
if ! [ -d "$MAILCHDIR" ]; then
    mkdir -p "$MAILCHDIR"
fi

################## terminate if $REFRAINFILE exists ##################

if [ -e "$REFRAINFILE" ]; then
    exit 0
fi

# Check for space in tmp
checktmpsize

############################### sleep #########################################

echo "CRON-APT RUN [$CONFIG]: $(date)" > "$INITLOG"
if [ -n "$RUNSLEEP" ] ; then
    if [ $RUNSLEEP -gt 0 ] ; then
	if [ -z "$RANDOM" ] ; then
            # A fix for shells that do not have this bash feature.
	    RANDOM=$(dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -c"1-5")
	fi
	TIME=$(($RANDOM % $RUNSLEEP))
	sleep $TIME
	echo "CRON-APT SLEEP: $TIME, $(date)" >> "$INITLOG"
	# Check refrain file again
	if [ -e "$REFRAINFILE" ]; then
	    exit 0
	fi
    fi
fi

# Check for space in tmp again
checktmpsize

################## try to take out a lock, terminate if unsuccessful ##########

if [ -x /usr/bin/dotlockfile ] ; then
    if ! dotlockfile -l -p -r 10 $LOCKFILE; then
        # create log entry
        echo > $MAIL "cannot acquire cron-apt lock."
	onexit
	exit 1
    fi
else
    if [ -z "$NOLOCKWARN" ]; then
        echo >  $TEMP "WARNING: dotlockfile not installed. If you don't want to see this"
        echo >> $TEMP "         Warning any more, set NOLOCKWARN in the configuration file."
	createerrorinfo "startup"
    fi
fi

################## initialize various log files ##############################

cp "$INITLOG" "$RUNMAIL"
cp "$INITLOG" "$RUNLOG"
cp "$INITLOG" "$RUNSYSLOG"
cp "$INITLOG" "$RUNERROR"
rm -f "$INITLOG"

############################### script ########################################
# Always run onexit before exit.

for ACTION in $(run_parts "$ACTIONDIR") ; do
	ACTIONF=$(echo $ACTION | sed "s|$ACTIONDIR/||")
	if [ -f "$ACTIONCONFDIR/$ACTIONF" ] ; then
	    . "$ACTIONCONFDIR/$ACTIONF"
	fi
	echo "CRON-APT ACTION: $ACTIONF" > "$ACTIONERROR"
	echo "CRON-APT ACTION: $ACTIONF" > "$ACTIONMAIL"
	echo "CRON-APT ACTION: $ACTIONF" > "$ACTIONLOG"
	echo "CRON-APT ACTION: $ACTIONF" > "$ACTIONSYSLOG"
	herevariables_store
	cat "$ACTIONDIR/$ACTIONF" | \
	    sed -e "s/#.*$//;" | \
	    grep -v "^[[:space:]]*$" | {
		while read LINE ; do
		    echo "CRON-APT LINE: $APTCOMMAND $LINE" > "$TEMP"
		    UMASK_SAVE=$(umask)
		    umask $UMASK_RELAXED
		    $APTCOMMAND $OPTIONS $LINE >> $TEMP 2>&1
		    RET=$?
		    umask $UMASK_SAVE
		    if [ $RET -ne 0 ]; then
		        # ---
			# An error has occured.
			createerrorinfo $ACTIONF
			
			# SYSLOG
			if [ "$SYSLOGON" = "error" ] || [ "$SYSLOGON" = "upgrade" ] ; then
			    createsysloginfo $ACTIONF
			fi

			# MAIL
			if [ "$MAILON" = "error" ] || [ "$MAILON" = "upgrade" ] ; then
			    createmailinfo $ACTIONF
			fi
			
			# DEBUG
			if [ "$DEBUG" = "error" ] ; then
			    createloginfo $ACTIONF
			fi

			# EXIT
			if [ "$EXITON" = "error" ] ; then
			    echo exit > "$STATUS"
			    #exit
			fi
		    else
		        # ---
			# No error has occured.
			
			if grep -q 'The following packages will be upgraded' "$TEMP" ; then
		            # ---
			    # Upgrade has happend

	                    # SYSLOG
			    if [ "$SYSLOGON" = "upgrade" ] ; then
				createsysloginfo $ACTIONF
			    fi
			    # MAIL
			    if [ "$MAILON" = "upgrade" ] ; then
				createmailinfo $ACTIONF
			    fi
			    # DEBUG
			    if [ "$DEBUG" = "upgrade" ] ; then
				createloginfo $ACTIONF
			    fi
			fi
		    fi
		    # ---
                    # Independent of error or not
			
		    # SYSLOG
		    if [ "$SYSLOGON" = "always" ] ; then
			createsysloginfo $ACTIONF
		    fi
		    # MAIL
		    if [ "$MAILON" = "always" ] ; then
			createmailinfo $ACTIONF
		    fi
		    # DEBUG
                    if [ "$DEBUG" = "verbose" ] || [ "$DEBUG" = "always" ] ; then
			createloginfo $ACTIONF
		    fi

		    if grep -qvE '^[[:space:]]*$|CRON-APT' "$TEMP"; then
		        # ---
			# Now we have output
			if [ "$SYSLOGON" = "output" ]; then
			    createsysloginfo $ACTIONF
			fi
			if [ "$MAILON" = "output" ]; then
			    createmailinfo $ACTIONF
			fi
			if [ "$DEBUG" = "upgrade" ] ; then
			    createloginfo $ACTIONF
			fi
		    fi
		    TLINE=$(echo "$LINE" | sed -e "s/[[:space:]]/_/g;" | \
			tr "/" "-")
		    if [ -n "$DIFFIGNORE" ]; then
			DIFFIGNORELINE="--ignore-matching-lines=$DIFFIGNORE"
		    fi
		    if [ ! -r "$MAILCHDIR/$ACTIONF-$TLINE" ] || ! diff $DIFFIGNORELINE -u "$MAILCHDIR/$ACTIONF-$TLINE" "$TEMP" > "$DIFF" ; then
			cp "$TEMP" "$MAILCHDIR/$ACTIONF-$TLINE"
			# What to do with diff
			# OBS! FROM NOW ON "$TEMP" CAN HAVE DIFF INFORMATION IN IT!
			if [ -n "$DIFF" ] && [ -r "$DIFF" ] ; then
			    if [ "$DIFFONCHANGES" = "only" ] ; then
				cp "$DIFF" "$TEMP"
			    elif [ "$DIFFONCHANGES" = "append" ] ; then
				cat "$DIFF" >> "$TEMP"
			    elif [ "$DIFFONCHANGES" = "prepend" ] ; then
				echo "----- DIFF END HERE -----" >> "$DIFF"
				cat "$TEMP" >> "$DIFF"
				mv "$DIFF" "$TEMP"
			    fi
			    rm -f "$DIFF"
			fi
		        # ---
			# We have changes

			# MAIL
			if [ "$MAILON" = "changes" ] ; then
			    createmailinfo $ACTIONF
			fi
			# SYSLOG
			if [ "$SYSLOGON" = "changes" ] ; then
			    createmailinfo $ACTIONF
			fi
			# DEBUG
			if [ "$DEBUG" = "changes" ] ; then
			    createloginfo $ACTIONF
			fi
		    fi
		done
		#exit
	    }
	if [ -e "$STATUS" ] ; then
	    if grep exit "$STATUS" > /dev/null 2>&1 ; then
		rm -f "$STATUS"
		onexit
		exit
	    fi
	    rm -f "$STATUS"
	fi
	herevariables_restore
done

onexit
