#!/bin/bash
#
#  yum-autoupdate
#     Designed to only send mail when something has been updated or installed
#
# Written by Troy Dawson <dawson@fnal.gov>
# Changed - May 7, 2003  by Troy Dawson <dawson@fnal.gov>
# Rewrite - May 14, 2004  by Troy Dawson <dawson@fnal.gov>
# Rewrite - March 1, 2005  by Troy Dawson <dawson@fnal.gov>
# Rewrite - January 25, 2011  by Troy Dawson <dawson@fnal.gov>
# Tweaks  - Oct 27, 2011 by Pat Riehecky <riehecky@fnal.gov>
# Tweaks  - Feb 1, 2012 by Pat Riehecky <riehecky@fnal.gov>
# BugFix  - Sep 6, 2012 by Pat Riehecky <riehecky@fnal.gov>
# BugFix  - Dec 27, 2012 by Pat Riehecky <riehecky@fnal.gov>
# BugFix  - Dec 5, 2014 by Pat Riehecky <riehecky@fnal.gov>
#     This rewrite (version 2) used a central config file for everything
#
# --------------------------------------------------------------
# Copyright 2003, 2004, 2005, 2011 Troy Dawson, Scientific Linux
# --------------------------------------------------------------
#
# This copyrighted material is made available to anyone wishing to use, modify,
# copy, or redistribute it subject to the terms and conditions of the GNU
# General Public License v.2.  This program is distributed in the hope that it
# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
# implied warranties 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. 
#
#########################
# VARIABLES
# Variables such as debug, and who to send the email to
#  are now in the config file /etc/sysconfig/yum-autoupdate
# There should be no reason to change this script unless
#  you are changing the functionality
#########################

. /etc/sysconfig/yum-autoupdate

#*******************************************
#random_sleep
# sleep for a random amount of minutes
#*******************************************
random_sleep() {
    utime=`cat /proc/uptime | cut -d'.' -f1`
    if [[ $utime -ge 72000 ]] ; then
        RAN=$RANDOM
        if [[ "$RAN" = "0" ]] ; then
            RAN=1
        fi
        if [[ $MAXWAITTIME -le 0 ]] ; then
            MAXWAITTIME=1
        fi
        SLEEPTIME=$(($RAN % $MAXWAITTIME))
        if [[ "$DEBUG" = "true" ]] ; then
            echo "  Sleeping for $SLEEPTIME minutes"
        fi
        sleep ${SLEEPTIME}m
    fi
}

#*******************************************
#
#etime_to_seconds
# determine how many seconds a process has
# been running (not necessarily active)
#*******************************************
etime_to_seconds() {
    echo $1 | awk -F $':' -f <(cat - <<-'EOF'
	{
	    if (NF == 2) {
		    print $1*60 + $2
		} else if (NF == 3) {
		    split($1, a, "-");
		    if (a[2] > 0) {
			    print ((a[1]*24+a[2])*60 + $2) * 60 + $3;
			} else {
			    print ($1*60 + $2) * 60 + $3;
			}
		}
	}
	EOF
    )
}

#*******************************************
#check_yum
# check to see if yum is running, and if it is, for how long
# If it has been running close to a day, it's time
# to kill it and try again.
#*******************************************
check_yum() {
    # if we were not asked to kill things, do not
    if [[ "$UNSTICK_STUCK_YUM" != 'true' ]]; then
        return
    fi

    if [[ -f /var/run/yum.pid ]] ; then
	if [[ "$DEBUG" = "true" ]] ; then
            echo "  Looking for long running yum processes..."
	fi
        YUMPID=$(cat /var/run/yum.pid)
        if [[ -d /proc/${YUMPID} ]] ; then
            YUMRUNTIME=$(etime_to_seconds $(ps -o etime= -p $YUMPID))
            SECONDS_IN_A_DAY=86400
            if [[ ${YUMRUNTIME} -ge ${SECONDS_IN_A_DAY} ]] ; then
                if [[ "$DEBUG" = "true" ]] ; then
                    echo "  Yum PID ${YUMPID}, Running for ${YUMRUNTIME} seconds"
                    echo "    This is more than the limit of ${SECONDS_IN_A_DAY} seconds (1 day) to execute"
                    echo "    Killing process ${YUMPID}"
                fi

                #one last sanity check before we kill things off
                grep -q yum /proc/${YUMPID}/stat
                if [[ "$?" = "0" ]] ; then
                    kill -9 ${YUMPID}
                fi
            else
                if [[ "$DEBUG" = "true" ]] ; then
                    echo "  Yum PID ${YUMPID}, Running for ${YUMRUNTIME} seconds"
                    echo "    This is less than the limit of ${SECONDS_IN_A_DAY} seconds (1 day)"
                    echo "    So I guess we'll wait until it is done..."
                fi
            fi
        else
            if [[ "$DEBUG" = "true" ]] ; then
                echo "  Yum PID ${YUMPID}, but no process running"
            fi
        fi
    else
        if [[ "$DEBUG" = "true" ]] ; then
            echo "  No other yum process running. Nothing to do"
        fi
    fi
}

#******************************
#Start the program
#But only if we're supposed to
#******************************
if [[ "$ENABLED" = "true" ]]; then
    if [[ "$DEBUG" = "true" ]] ; then
        echo "Yum crontab starting"
    fi
#****************************
#VARIABLES
#****************************
    CHANGE=0
    ERROR=0
    TEMPFILE="`mktemp /tmp/yum.temp.XXXXXXXXXX`"
    TEMPCONFIGFILE="`mktemp /tmp/yum.temp.config.XXXXXXXXXX`"
    TEMPPRERUNFILE="`mktemp /tmp/yum.temp.prerun.XXXXXXXXXX`"
    TEMPADDONRUNFILE="`mktemp /tmp/yum.temp.addonrun.XXXXXXXXXX`"
    TEMPMAILFILE="`mktemp /tmp/yum.temp.mail.XXXXXXXXXX`"
    TEMPPOSTRUNFILE="`mktemp /tmp/yum.temp.postrun.XXXXXXXXXX`"
    TEMPRANDFILE="`mktemp /tmp/yum.temp.rand.XXXXXXXXXX`"
    TODAY=`date +%Y-%m-%d`

#****************************
#We are doing our own sleep here
#instead of in yum so that each
#of the modules can be added
#without worrying which one
#has the randome time in it
#****************************
    random_sleep

#****************************
#We are are checking the yum process
#here because we were finding that 
#yum would get stuck with the right
#ftp problems
#****************************
    check_yum

#****************************
#Note if we are using yum --security or not
#****************************
    if [[ "$USE_YUMSEC" == "true" ]]; then
        if [[ "$DEBUG" = "true" ]] ; then
            echo "The yum-security plugin is enabled"
        fi
        SECURITY='--security'
    else
        if [[ "$DEBUG" = "true" ]] ; then
            echo "The yum-security plugin is disabled"
        fi
        SECURITY=''
    fi

#****************************
#Note if we are using yum --skip-broken or not
#****************************
    if [[ "$SKIP_BROKEN" == "true" ]]; then
        if [[ "$DEBUG" = "true" ]] ; then
            echo "The yum --skip-broken enabled"
        fi
        SKIP_BROKEN='--skip-broken'
    else
        if [[ "$DEBUG" = "true" ]] ; then
            echo "The yum will not use --skip-broken"
        fi
        SKIP_BROKEN=''
    fi

#****************************
#PRERUN
#****************************
# Allow certian customizations before running
#****************************
    if [[ "$PRERUN" != "" ]]; then
        if [[ "$DEBUG" = "true" ]] ; then
            echo "Executing PRERUN: $PRERUN"
        fi

        # to avoid selinux denial, we are using cat since we don't care about $?
        if [[ "$SENDONLYERRORS" = "true" ]] ; then
            # this makes STDERR=STDOUT and then changes STDOUT
            eval $PRERUN 2>&1 >/dev/null | cat > $TEMPPRERUNFILE
        else
            eval $PRERUN 2>&1 | cat > $TEMPPRERUNFILE
        fi

        if [[ "$DEBUG" = "true" ]] ; then
            echo "PRERUN output:"
            cat $TEMPPRERUNFILE
        fi

        if [[ -s $TEMPPRERUNFILE ]]; then
            echo " --------------------" >> $TEMPMAILFILE
            echo " YUM - PRE" >> $TEMPMAILFILE
            echo " --------------------" >> $TEMPMAILFILE
            cat $TEMPPRERUNFILE >> $TEMPMAILFILE
            echo "" >> $TEMPMAILFILE
        fi
    fi

#****************************
#ADDONRUN
#****************************
# Silently run this script unless it wasn't silent
#****************************
    if [[ "$ADDONRUN" != "" ]]; then
        if [[ "$DEBUG" = "true" ]] ; then
            echo "Executing ADDONRUN: $ADDONRUN"
        fi

        # to avoid selinux denial, we are using cat since we don't care about $?
        if [[ "$SENDONLYERRORS" = "true" ]] ; then
            # this makes STDERR=STDOUT and then changes STDOUT
            eval $ADDONRUN 2>&1 >/dev/null | cat > $TEMPADDONRUNFILE
        else
            eval $ADDONRUN 2>&1 | cat > $TEMPADDONRUNFILE
        fi

        if [[ "$DEBUG" = "true" ]] ; then
            echo "ADDONRUN output:"
            cat $TEMPADDONRUNFILE
        fi

        if [[ -s $TEMPADDONRUNFILE ]]; then
            echo " --------------------" >> $TEMPMAILFILE
            echo " YUM - ADDON" >> $TEMPMAILFILE
            echo " --------------------" >> $TEMPMAILFILE
            cat $TEMPADDONRUNFILE >> $TEMPMAILFILE
            echo "" >> $TEMPMAILFILE
        fi
    fi

#****************************
#START Errata MODULE
#****************************
#The errata module is to install
#errata updates to the packages
#already on a machine.
#****************************
    MODULE="security"

    if [[ "$DEBUG" = "true" ]] ; then
        echo "********** Starting ***** $MODULE ***** STARTING ********"
        echo "   Creating Config File"
    fi
#****************************
# Create yum.conf for this module
# We will do that by simply taking the original yum.conf file
# and adding the yum.cron.excludes to it
#****************************
    grep -q -e '^[ \t]*exclude=' $CONFIGFILE
    if [[ $? -eq 0 ]] ; then
        excludelist="$EXCLUDE"
        cat $CONFIGFILE | sed -e "s:exclude=.*:& $excludelist:" > $TEMPCONFIGFILE
    else
        excludelist="exclude=$EXCLUDE"
        cat $CONFIGFILE | sed -e "s:\[main\].*:&\n$excludelist:" > $TEMPCONFIGFILE
    fi
    if [[ "$DEBUG" = "true" ]] ; then
        echo "*********START** $MODULE YUM.CONF FILE **START**********"
        cat $TEMPCONFIGFILE
        echo "**********END*** $MODULE YUM.CONF FILE ***END***********"
    fi
#****************************
#Run yum and record if anything happens
#****************************
    if [[ "$CHECK_ONLY" = "true" ]] ; then
        update_cmd="check-update"
    else
        update_cmd="update"
    fi
    if [[ "$DEBUG" = "true" ]] ; then
        echo "    Starting Yum with command"
        echo "     yum -c $TEMPCONFIGFILE -e 0 -d 1 -y $SECURITY $SKIP_BROKEN ${update_cmd}"
    fi

# to avoid selinux denial:
#    we are using a subshell so the file handles for stdout/err are not inherited
#    this way we can check $? while not letting procs inherited from yum default
#    to our output file handel in /tmp/ (tmp_t context)
#
# example audit log:
#   avc:  denied  { write } for  pid=XXXX comm="groupadd" path="/tmp/yum.temp.XXXXXXXXXX" dev=sda2 ino=XXXXXXX scontext=user_u:system_r:groupadd_t:s0 tcontext=user_u:object_r:tmp_t:s0 tclass=file 
#   avc:  denied  { write } for  pid=XXXX comm="useradd" path="/tmp/yum.temp.XXXXXXXXXX" dev=sda2 ino=XXXXXXX scontext=user_u:system_r:useradd_t:s0 tcontext=user_u:object_r:tmp_t:s0 tclass=file 
#   avc:  denied  { write } for  pid=XXXX comm="restorecon" path="/tmp/yum.temp.XXXXXXXXXX" dev=sda2 ino=XXXXXXX scontext=user_u:system_r:restorecon_t:s0 tcontext=user_u:object_r:tmp_t:s0 tclass=file 
    YUMOUT="$(yum -c $TEMPCONFIGFILE -e 0 -d 1 -y $SECURITY $SKIP_BROKEN ${update_cmd} 2>&1)"
    if [[ $? -ne 0 ]] ; then
        ERROR=1
    fi
    # avoid un-necessary \n if $YUMOUT is empty
    echo -n "$YUMOUT" > $TEMPFILE

    if [[ -s $TEMPFILE ]] ; then
        # put the trailing \n back in if we have something to report
        echo >> $TEMPFILE
        if [[ "$DEBUG" = "true" ]] ; then
            echo "    We had changes"
            echo "*********START** ${MODULE} CHANGES FILE **START**********"
            cat $TEMPFILE
            echo "**********END*** ${MODULE} CHANGES FILE ***END***********"
        fi
        CHANGE=1
        echo " --------------------" >> $TEMPMAILFILE
        echo " YUM - ${MODULE}" >> $TEMPMAILFILE
        echo " --------------------" >> $TEMPMAILFILE
        cat $TEMPFILE >> $TEMPMAILFILE
        echo "" >> $TEMPMAILFILE
    fi
#****************************
#Cleanup ${MODULE}
#****************************
    if [[ "$DEBUG" = "true" ]] ; then
        echo " Cleaning Up ${MODULE}"
    fi
    yum -e 0 -d 0 -c $TEMPCONFIGFILE clean packages

    if [[ "$DEBUG" = "true" ]] ; then
        mv -f $TEMPCONFIGFILE /tmp/yum.conf.${MODULE}
    fi
#****************************
#END Errata MODULE
#****************************

#****************************
#POSTRUN
#****************************
    if [[ "$POSTRUN" != "" ]]; then
        if [[ "$DEBUG" = "true" ]] ; then
            echo "Executing POSTRUN: $POSTRUN"
        fi

        # to avoid selinux denial, we are using cat since we don't care about $?
        if [[ "$SENDONLYERRORS" = "true" ]] ; then
            # this makes STDERR=STDOUT and then changes STDOUT
            eval $POSTRUN 2>&1 >/dev/null | cat > $TEMPPOSTRUNFILE
        else
            eval $POSTRUN 2>&1 | cat > $TEMPPOSTRUNFILE
        fi

        if [[ "$DEBUG" = "true" ]] ; then
            echo "POSTRUN output:"
            cat $TEMPPOSTRUNFILE
        fi

        if [[ -s $TEMPPOSTRUNFILE ]]; then
            echo " --------------------" >> $TEMPMAILFILE
            echo " YUM - POST" >> $TEMPMAILFILE
            echo " --------------------" >> $TEMPMAILFILE
            cat $TEMPPOSTRUNFILE >> $TEMPMAILFILE
            echo "" >> $TEMPMAILFILE
        fi
    fi

#****************************
#Send Mail If Needed
# Verify the user wants email
#  Verify there has been changes of some type
#   See if the user has errors-only set.  
#     If errors-only is false, send the email
#     If errors-only is true, then check to see if there were any errors
#       If there were errors, then send the email
#****************************
    if ! [[ "$SENDEMAIL" = "false" ]] ; then
        if [[ -s $TEMPMAILFILE ]]; then
            if [[ $CHANGE -eq 1 ]] ; then
                if [[ "$SENDONLYERRORS" = "true" ]] ; then
                    if [[ $ERROR -eq 1 ]] ; then
                        if [[ "$DEBUG" = "true" ]] ; then
                            echo " SENDEMAIL: $SENDEMAIL  SENDONLYERRORS: $SENDONLYERRORS  ERROR: $ERROR"
                            echo " Mail Needs To Be Sent"
                            echo "  /bin/mail -s \"YUM:$HOSTNAME:$TODAY\" $MAILLIST "
                        fi
                        cat $TEMPMAILFILE | tr -d '\015' | /bin/mail -s "YUM:$HOSTNAME:$TODAY" $MAILLIST
                    fi
                else
                    if [[ "$DEBUG" = "true" ]] ; then
                        echo " SENDEMAIL: $SENDEMAIL  SENDONLYERRORS: $SENDONLYERRORS  ERROR: $ERROR"
                        echo "   Mail Needs To Be Sent"
                        echo "    /bin/mail -s \"YUM:$HOSTNAME:$TODAY\" $MAILLIST "
                    fi
                    cat $TEMPMAILFILE | tr -d '\015' | /bin/mail -s "YUM:$HOSTNAME:$TODAY" $MAILLIST
                fi
            fi
        fi
    fi

#****************************
#Cleanup
#****************************
    if [[ "$DEBUG" = "true" ]] ; then
        echo " Cleaning Up Interactive Yum"
    fi
    yum -e 0 -d 0 clean packages > /dev/null 2>&1
    yum -e 0 -d 0 provides xyz   > /dev/null 2>&1

    if [[ "$DEBUG" = "true" ]] ; then
        echo " Cleaning Up TEMPFILES"
    fi
    rm -f $TEMPPRERUNFILE
    rm -f $TEMPADDONRUNFILE
    rm -f $TEMPMAILFILE
    rm -f $TEMPPOSTRUNFILE
    rm -f $TEMPCONFIGFILE
    rm -f $TEMPRANDFILE
    rm -f $TEMPFILE

    if [[ "$DEBUG" = "true" ]] ; then
        echo "Yum-Autoupdate Exiting succesfully"
    fi
else
    if [[ "$DEBUG" = "true" ]] ; then
        echo "Yum-Autoupdate not enabled.  Exiting"
    fi
    exit 1
fi
exit 0
