#!/bin/sh
#
#SSANotifier: a system daemon for automatic check of security patches.
#it runs on all slackware system (including slack ports to other architectures?)
#it is written in ash so you can run it under your preferred shell.
#
#v.0.90.0
#
#to do:
#-prova gdialog
#-prova xdialog


#--------------------------------------------------
#general settings

UPDATER="/usr/libexec/SSANotifier/SSAUpdater"
#UPDATER="/home/utente/slack/scripting/SSANotifier/devel/SSAUpdater"	#just for debugging

TMPDIR="/tmp/SSANotifier"
export TMPDIR

CONFDIR="/etc/SSANotifier"
#CONFDIR="/home/utente/slack/scripting/SSANotifier/devel"	#just for debugging
CONFFILE=$CONFDIR/"SSANotifier.conf"
export CONFDIR
export CONFFILE

PATCHLIST="FILE_LIST"
MD5LIST="CHECKSUMS.md5"
export PATCHLIST
export MD5LIST

#load settings
. $CONFFILE
#--------------------------------------------------


#--------------------------------------------------
#check for notification type
case $NOTIFY in
"KDE")
	[ $(which kdialog) ] || { echo "$0 ABORT: kdialog not installed"; exit 1; }
	DIALOGBASE=$(which kdialog)
	[ $(which kdesu) ] || { echo "$0 ABORT: kdesu not installed"; exit 1; }
	SUGUI=$(which kdesu)
	SUGUIOPTS="--noignorebutton -d -n -t -c"
	PASSIVE="--passivepopup"
	SIZE1="700 300"
	SIZE2=""
	SIZE3="800 600"
	SIZE4="200"
	SIZE5="1000"
;;
"SHELL")
	[ $(which dialog) ] || { echo "$0 ABORT: dialog not installed"; exit 1; }
	DIALOGBASE=$(which dialog)
	SUGUI=$(which su)
	SUGUIOPTS="-c"
	PASSIVE="--infobox"
	SIZE1="20 80"
	SIZE2="10 80"
	SIZE3="20 80"
	SIZE4="10 80"
	SIZE5="10 80"
;;
"GNOME")
	[ $(which zenity) ] || { echo "$0 ABORT: gdialog/zenity not installed"; exit 1; }
	DIALOGBASE=$(which zenity)
	[ $(which gksu) ] || { echo "$0 ABORT: gksu not installed"; exit 1; }
	SUGUI=$(which gksu)
	SUGUIOPTS="--description SSANotifier -u root -w"
	PASSIVE="--progress --pulsate --auto-close"
	SIZE1="700"
	SIZE1A="300"
	SIZE2=""
	SIZE3="800 600"
	SIZE4="200"
	SIZE5="1000"
;;
*)
	echo "$0 ABORT: selected notification not supported"
	exit 1
;;
esac
#--------------------------------------------------


#--------------------------------------------------
#"resource table" with some messages displayed on screen
TITLE="SSANotifier"
EMESSAGE="Abort. Another istance of $0 is running. If this is wrong, remove file /var/lock/SSANotifier and restart application."
UMESSAGE="Upgrade the system?"
DMESSAGE="Currently downloading:"
FMESSAGE="Upgrading the system"
GMESSAGE="please wait..."
export UMESSAGE
export DMESSAGE
export FMESSAGE
export GMESSAGE
#--------------------------------------------------


#--------------------------------------------------
#security check
[ -f /var/lock/SSANotifier ] && {
	if [ ! $NOTIFY = "GNOME" ]; then { 
		$DIALOGBASE --title "$TITLE" --msgbox "$EMESSAGE" $SIZE2 
	} else {
		$DIALOGBASE --title "$TITLE" --error --text="$EMESSAGE" $SIZE2
	}
	fi
#	echo "$0 ABORT: another istance of $0 is running"
	exit 0;
}
touch /var/lock/SSANotifier
#--------------------------------------------------


#--------------------------------------------------
#auxiliary functions

CompareVersion ()
{
##
## compare the version and the build string of two packages
## the fisrt one (defined by the prefix P as Patch) is expected to be newer than the second
## (defioned by the prefix I as Installed) if so the function return 1 else return 0
##

#echo $1
#echo $2

#retrieve patch version: to compare the version number we need to sanitize it!
#the sed command removes any 'dot' (from 2.3.45 to 2345) and any letter (from 6u2 to 62 - look at jre numbering scheme)
PVERSION=$(echo "$1" | awk -F - '{printf $(NF-2)}' | sed -e "s|[a-zA-Z]||g; s|_||g;")
IVERSION=$(echo "$2" | awk -F - '{printf $(NF-2)}' | sed -e "s|[a-zA-Z]||g; s|_||g;")

#  echo $PVERSION
#  echo $IVERSION

PMAJORV=$(echo $PVERSION | cut -f1 -d.)
IMAJORV=$(echo $IVERSION | cut -f1 -d.)

[ $PMAJORV -lt $IMAJORV ] && return 0;
[ $PMAJORV -gt $IMAJORV ] && return 1;

#if we have the same major version look at minor...
[ $PMAJORV -eq $IMAJORV ] && {
	PMINORV=$(echo $PVERSION | cut -f2 -d.)
	IMINORV=$(echo $IVERSION | cut -f2 -d.)
	
	[ $PMINORV -lt $IMINORV ] && return 0;
	[ $PMINORV -gt $IMINORV ] && return 1;
	
	#if we have the same minor version look at the full string...
	[ $PMINORV -eq $IMINORV ] && {
		PVERSION=$(echo "$1" | awk -F - '{printf $(NF-2)}' | sed -e "s|\.||g; s|[a-zA-Z]||g; s|_||g;")
		IVERSION=$(echo "$2" | awk -F - '{printf $(NF-2)}' | sed -e "s|\.||g; s|[a-zA-Z]||g; s|_||g;")

		[ $PVERSION -lt $IVERSION ] && return 0;
		[ $PVERSION -gt $IVERSION ] && return 1;
		
		#if we have the same version look at the build...
		[ $PVERSION -eq $IVERSION ] && {
			#looking at recent patches, it seems that PJV uses a suffix to define the distro version
			#usually the build string is something like "X" or "X_slackV", where X is the build number and V is the
			#slackware version
			PBUILD=$(echo $(basename $1 .tgz) | awk -F - '{printf $(NF)}' | cut -f1 -d "_" | sed -e "s|\.||g; s|[a-zA-Z]||g; s|_||g;")
			IBUILD=$(echo $(basename $2 .tgz) | awk -F - '{printf $(NF)}' | cut -f1 -d "_" | sed -e "s|\.||g; s|[a-zA-Z]||g; s|_||g;")
		
		# 	echo $PBUILD
		# 	echo $IBUILD
		
			[ $PBUILD -lt $IBUILD ] && return 0;
			[ $PBUILD -gt $IBUILD ] && return 1;
			[ $PBUILD -eq $IBUILD ] && return 2;	#same match
		}
	}
}

}



GetObsolete ()
{
##
##check for every obsolete patch still stored inside the local mirror
##if we have 2 or more versions of the same patch we delete the old ones
##

TMPPATCHES=$(ls *.tgz)
for cFILE in $TMPPATCHES; do
	#discard arch version and build infos
	cNAME=$(echo $(basename $cFILE) | awk -F - '{for (i=1; i<NF-3; i++) {printf $i"-"}; printf $(NF-3)}')
	#get all patches matching the cNAME pattern (including the file itself)
	OTHERVERS=$(echo $TMPPATCHES | sed -e 's| |\n|g' | grep $cNAME)
	#look for spourious matches
	for aMATCH in $OTHERVERS; do
		#discard arch version and build infos
		aNAME=$(echo $(basename $aMATCH) | awk -F - '{for (i=1; i<NF-3; i++) {printf $i"-"}; printf $(NF-3)}')
		[ "$aNAME" = "$cNAME" ] && {
			CompareVersion $cFILE $aMATCH
			#if a match is obsolete
			[ $? -eq 1 ] && OBSOLETEPATCHES="$OBSOLETEPATCHES $aMATCH"
		}
	done
done
}



GetOverridden ()
{
##
##check for every obverriden patch stored inside the local mirror
##if we have a newer version of the patch in the remote server, we mark local version as
##candidates for deletion
##
#load all already downloaded patches
TMPPATCHES=$(ls *.tgz)

#clean up the list of patches retrieving all and only pending upgrades
for FILE in $TMPPATCHES; do
	#if the patch is marked as removeable we skip it!
	ISOLD=$(echo $OBSOLETEPATCHES | sed -e 's| |\n|g' | grep $FILE)
	[ -n "$ISOLD" ] && continue	
	
	#retrieve the patch name discarding version, arch and build informations
	FNAME=$(echo $FILE | awk -F - '{for (i=1; i<NF-3; i++) {printf $i"-"}; printf $(NF-3)}')
	#look for new patches with same name	
	NEWVERS=$(echo $NEWPATCHES | sed -e 's| |\n|g' | grep $FNAME)
	#look for spourious matches
	for aNEWMATCH in $NEWVERS; do
		#discard arch version and build infos
		aNEWNAME=$(echo $(basename $aNEWMATCH) | awk -F - '{for (i=1; i<NF-3; i++) {printf $i"-"}; printf $(NF-3)}')
		[ "$aNEWNAME" = "$FNAME" ] && {
			CompareVersion $aNEWMATCH $FILE
			#if a match is newer we mark the patch as overriddenable
			[ $? -eq 1 ] && OVERRIDDENPATCHES="$OVERRIDDENPATCHES $FILE"
		}
	done
done
}



GetPending ()
{
##
## define pending upgrades: this is useful if we have aborted a previous upgrade session of if we
## have installed some NEW packages in the while of two upgrade sessions
##

#load all already downloaded patches
TMPPATCHES=$(ls *.tgz)

#clean up the list of patches retrieving all and only pending upgrades
for FILE in $TMPPATCHES; do
	#if the patch is marked as removeable we skip it!
	ISOLD=$(echo $OBSOLETEPATCHES | sed -e 's| |\n|g' | grep $FILE)
	[ -n "$ISOLD" ] && continue	
	
	#retrieve the patch name discarding version, arch and build informations
	FNAME=$(echo $FILE | awk -F - '{for (i=1; i<NF-3; i++) {printf $i"-"}; printf $(NF-3)}')
	
	#look if we find any version installed in the system
	INSTALLED=$(ls /var/log/packages | grep -w "$FNAME" 2>/dev/null)
	#we can have more matches
	#(eg. if we grep the "gimp" patch we can find both "gimp-print" and "gimp" packages)

	for nFILE in $INSTALLED; do
		#retrieve the file name of the installed package discarding version, arch and build infos
		INAME=$(echo $nFILE | awk -F - '{for (i=1; i<NF-3; i++) {printf $i"-"}; printf $(NF-3)}')
		#found exactly the same package (this discards "gimp-print" if we are patching the "gimp" package)
		[ "$INAME" = "$FNAME" ] && {
			#look if an older version is installed
			#(otherwise we drop the patch automatically)
			CompareVersion $FILE $nFILE
			RETVAL=$?
			[ $RETVAL -eq 1 ] && {
				echo "+ downloaded patch $FILE is newer than installed package $nFILE"
				PENDINGPATCHES="$PENDINGPATCHES $FILE"
				#if there also be a new patch we will delete this patch AFTER a correct d.load
			} #for nFILE in $INSTALLED;
		}
	done #for nFILE in $INSTALLED;
done #for FILE in $CANDIDATEPATCHES;
}

#NULLA  ANCORA STATO TESTATO!!!!!!!!!!
#--------------------------------------------------


#--------------------------------------------------
#entry point

## check for environment

#look if we found the upgradepkg utility
[ -f $UPGRADEPKG ] || { echo "$0 ABORT: upgrade utility is not installed"; rm /var/lock/SSANotifier; exit 1; }

#look if we have setted a download mirror!
[ -z $SERVER ] && { echo "$0 ABORT: no server selected"; rm /var/lock/SSANotifier; exit 1; }

#look if we have selected a dir where we can store our patches
[ -z $LOCALMIRROR ] && { echo "$0 ABORT: no local mirror selected"; rm /var/lock/SSANotifier; exit 1; }

#look if wget is installed and in our path
[ $(which wget) ] || { echo "$0 ABORT: wget is not installed"; rm /var/lock/SSANotifier; exit 1; }
WGET=$(which wget)
export WGET

#look to the md5 utility
[ $(which md5sum) ] || { echo "$0 ABORT: md5sum is not installed"; rm /var/lock/SSANotifier; exit 1; }
MD5SUM=$(which md5sum)
export MD5SUM

#look if we have enabled the gpg key check. if so, look to the gpg utility
[ $CHECKGPG -eq 1 ] && {
	[ $(which gpg2) ] || { echo "$0 ABORT: gpg2 is not installed"; rm /var/lock/SSANotifier; exit 1; }
	GPG=$(which gpg2)
	export GPG
	#look for gpg key location
	[ -z $GPGKEY ] && { echo"$0 ABORT: gpg public key not defined"; rm /var/lock/SSANotifier; exit 1; }
}

#update patches list
[ ! -d $TMPDIR ] && {
	mkdir -p $TMPDIR || {
		echo "$0 ABORT: unable to create filelist cache";
		rm /var/lock/SSANotifier; exit 1;
	}
} || {
	rm -Rf $TMPDIR/*
}

NEWLIST=$SERVER/"$PATCHLIST"
$WGET $WGETOPTS -P $TMPDIR "$NEWLIST"
[ $? -eq 1 ] && { echo "$0 ABORT: unable to find a changelog file"; rm /var/lock/SSANotifier; exit 1; }  #is this a bug or there is not any patch?

#check if an older list is available
NEWLIST=$TMPDIR/"$PATCHLIST"
export NEWLIST
OLDLIST=$LOCALMIRROR/$PATCHLIST".old"
export OLDLIST

[ -f "$OLDLIST" ] && {
	#if we have an older list we check if something has changed
	OLD=$($MD5SUM "$OLDLIST" | cut -f1 -d " ")
	NEW=$($MD5SUM "$NEWLIST" | cut -f1 -d " ")
	[ $NEW = $OLD ] && NEWLIST="" 	#no new updates
}

#just an auxiliary variable used by some clean-up functions...
TMPPATCHES=""
# define pending upgrades
PENDINGPATCHES=""
export PENDINGPATCHES
# define an empty list of new patches to be downloaded
NEWPATCHES=""
export NEWPATCHES
#define an empty list to store old version of patches. these packages are to be
#removed because no longer useful
OBSOLETEPATCHES=""
export OBSOLETEPATCHES
#define an empty list to store old version of patches. these packages are to be
#removed after the download (and check) of a new version (do you remember the many mozilla patches?...)
OVERRIDDENPATCHES=""
export OVERRIDDENPATCHES

[ ! -z "$NEWLIST" ] && {
#if we have some new upgrade...

	#extract all available patches defined in the the changelog file
	PATCHES=$(grep "\.tgz$" $NEWLIST 2>/dev/null | cut -f 2-3 -d /)

#echo ${PATCHES}

	#check for new patches...
	for FILE in $PATCHES; do
		[ -f $LOCALMIRROR/$(basename $FILE) ] || {
			NEWPATCHES="$NEWPATCHES $FILE"
		}
	done
}

[ -d $LOCALMIRROR ] && {

	cd $LOCALMIRROR
	#load pending patches (this modify the PENDINGPATCHES variable)
	#and look for obsolete ones (this modify the OBSOLETEPATCHES variable)
	GetObsolete
	GetOverridden
	GetPending
}

#clean up
OBSOLETEPATCHES=$(echo $OBSOLETEPATCHES | sed -e 's| |\n|g' | sort | uniq)
OVERRIDDENPATCHES=$(echo $OVERRIDDENPATCHES | sed -e 's| |\n|g' | sort | uniq)
PENDINGPATCHES=$(echo $PENDINGPATCHES | sed -e 's| |\n|g' | sort | uniq)

# echo
# echo "will be downloaded:"
# echo $NEWPATCHES | sed -e 's| |\n|g'
# echo
# echo "will be deleted:"
# echo $OBSOLETEPATCHES | sed -e 's| |\n|g'
# echo
# echo "are pending (if not overridden):"
# echo $PENDINGPATCHES | sed -e 's| |\n|g'
# echo
# echo "will be overridden (and maybe deleted):"
# echo $OVERRIDDENPATCHES | sed -e 's| |\n|g'
# rm /var/lock/SSANotifier;
# exit

#if we haven't a new list and there is no pending upgrade (and also no clean up is required) this means that we can exit
[ -z "$NEWPATCHES" ] && [ -z "$PENDINGPATCHES" ] && [ -z "$OBSOLETEPATCHES" ] && {
	echo "$0 ABORT: nothing changed"

	rm /var/lock/SSANotifier;
	exit 0;
}

## it is time to printout something and wait for user response...

#create a dialog for user response...
TMPFILE=$TMPDIR/message-$(date +%Y%m%d%H%M%S)

echo "--> The following security fixes are available at $SERVER:" > $TMPFILE
for FILE in $NEWPATCHES; do
	echo "    $(basename $FILE)" >> $TMPFILE
done

echo "--> The following security fixes are pending since last update (and maybe overridden by new patches):" >> $TMPFILE
for FILE in $PENDINGPATCHES; do
	echo "    $(basename $FILE)" >> $TMPFILE
done

echo "--> The following security fixes are overridden by new downloadable patches:" >> $TMPFILE
for FILE in $OVERRIDDENPATCHES; do
	echo "    $(basename $FILE)" >> $TMPFILE
done

echo "--> The following security fixes are obsolete and will be removed from cache:" >> $TMPFILE
for FILE in $OBSOLETEPATCHES; do
	echo "    $(basename $FILE)" >> $TMPFILE
done

if [ ! $NOTIFY = "GNOME" ]; then {
	$DIALOGBASE --title "$TITLE" --textbox "$TMPFILE" $SIZE1
} else {
	$DIALOGBASE --title "$TITLE" --text-info --filename="$TMPFILE" --width=$SIZE1 --height=$SIZE1A
}
fi

rm "$TMPFILE"

if [ ! $NOTIFY = "GNOME" ]; then { 
	$DIALOGBASE --title "$TITLE" --yesno "$UMESSAGE" $SIZE2 
} else {
	$DIALOGBASE --title "$TITLE" --question --text="$UMESSAGE" $SIZE2
}
fi

[ $? -eq 1 ] && { rm /var/lock/SSANotifier; exit 0; } #abort upgrade because of user request

## if we have "green light" we update the system via SSAUpdater ;-)

#just useful for shell interface
echo; echo "Root user identification:";

$SUGUI $SUGUIOPTS "$UPDATER"

rm /var/lock/SSANotifier;