Notificatore e installatore automatico di security fixes per Slackware
Da Slacky.eu.
(Reindirizzamento da Notificatore e installatore auotmatico di security fixes per Slackware)
Indice |
Descrizione
Notificatore e installatore automatico di security fixes che controlla il checksum e la firma digitale dei pacchetti prima di upgradarli...
Download
http://www.slacky.eu/misto/concorso/absinthe/SSANotifier-0.90.0/SSANotifier
http://www.slacky.eu/misto/concorso/absinthe/SSANotifier-0.90.0/SSANotifier.conf.new
http://www.slacky.eu/misto/concorso/absinthe/SSANotifier-0.90.0/SSAUpdater
http://www.slacky.eu/misto/concorso/absinthe/ssanotifier-0.90.0-noarch-1ab.tgz
Script SSANotifier
#!/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
#--------------------------------------------------
#security check
[ -f /var/lock/SSANotifier ] && {
echo "$0 ABORT: another istance of $0 is running"
exit 0;
}
touch /var/lock/SSANotifier
#--------------------------------------------------
#--------------------------------------------------
#general settings
TMPDIR=/tmp/SSANotifier
UPDATER="/usr/libexec/SSANotifier/SSAUpdater"
#UPDATER="/home/utente/slack/scripting/SSANotifier/devel/SSAUpdater" #just for debugging
CONFDIR="/etc/SSANotifier"
#CONFDIR="/home/utente/slack/scripting/SSANotifier/devel" #just for debugging
CONFFILE=$CONFDIR/"SSANotifier.conf"
PATCHLIST="FILE_LIST"
MD5LIST="CHECKSUMS.md5"
#--------------------------------------------------
#--------------------------------------------------
#auxiliary functions
function 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
##
#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 $FILE | awk -F - '{printf $(NF-2)}' | sed -e "s|\.||g; s|[a-zA-Z]||g")
IVERSION=$(echo $nFILE | awk -F - '{printf $(NF-2)}' | sed -e "s|\.||g; s|[a-zA-Z]||g")
# echo $PVERSION
# echo $IVERSION
[ $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 pat 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 $FILE .tgz) | awk -F - '{printf $(NF)}' | cut -f1 -d "_")
IBUILD=$(echo $(basename $nFILE .tgz) | awk -F - '{printf $(NF)}' | cut -f1 -d "_")
# echo $PBUILD
# echo $IBUILD
[ $PBUILD -le $IBUILD ] && return 0;
[ $PBUILD -gt $IBUILD ] && return 1;
}
}
function 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
CANDIDATEPATCHES=$(ls *.tgz)
#clean up the list of patches retrieving all and only pending upgrades
for FILE in $CANDIDATEPATCHES; do
#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
[ $? -eq 1 ] && {
#look if a new version of a patch is in the new list,
#if it isn't, we add the pending patch to the list
OBSOLETELIST=$(echo $NEWPATCHES | grep $FNAME 2>/dev/null)
#once again we can have more matches... (gimp and gimp-print, for example)
OBSOLETE=0
for nnFILE in $OBSOLETELIST; do
ONAME=$(echo $nnFILE | awk -F - '{for (i=1; i<NF-3; i++) {printf $i"-"}; printf $(NF-3)}')
# if we have a match, go over and search for other patches
[ "$INAME" = "$FNAME" ] && {
OBSOLETE=1
OBSOLETEPATCHES="$OBSOLETEPATCHES $FNAME"
break
}
done
#if there is not a new version on the server, we add the file to the pending list
[ $OBSOLETE -eq 0 ] && PENDINGPATCHES="$PENDINGPATCHES $FILE"
}
break; #for nFILE in $INSTALLED;
}
done #for nFILE in $INSTALLED;
done #for FILE in $CANDIDATEPATCHES;
}
#--------------------------------------------------
#--------------------------------------------------
#"resource table" with some messages displayed on screen
TITLE="SSANotifier"
UMESSAGE="Upgrade the system?"
DMESSAGE="Currently downloading:"
FMESSAGE="Upgrading the system"
GMESSAGE="please wait..."
#--------------------------------------------------
#--------------------------------------------------
#entry point
#load settings
. $CONFFILE
## 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; }
}
#check for notification type
case $NOTIFY in
"KDE")
[ $(which kdialog) ] || { echo "$0 ABORT: kdialog not installed"; rm /var/lock/SSANotifier; exit 1; }
DIALOGBASE=$(which kdialog)
[ $(which kdesu) ] || { echo "$0 ABORT: kdesu not installed"; rm /var/lock/SSANotifier; exit 1; }
SUGUI=$(which kdesu)
SUGUIOPTS="--noignorebutton -d -n -t -c"
PASSIVE="--passivepopup"
SIZE1="500 300"
SIZE2=""
SIZE3="800 600"
SIZE4="200"
SIZE5="1000"
;;
"SHELL")
[ $(which dialog) ] || { echo "$0 ABORT: dialog not installed"; rm /var/lock/SSANotifier; 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"; rm /var/lock/SSANotifier; exit 1; }
DIALOGBASE=$(which zenity)
[ $(which gksu) ] || { echo "$0 ABORT: gksu not installed"; rm /var/lock/SSANotifier; exit 1; }
SUGUI=$(which gksu)
SUGUIOPTS="-u root -w"
PASSIVE="--progress --pulsate --auto-close"
SIZE1="500 300"
SIZE2=""
SIZE3="800 600"
SIZE4="200"
SIZE5="1000"
;;
*)
echo "$0 ABORT: selected notification not supported"
exit 1
;;
esac
#update patches list
[ ! -d $TMPDIR ] && {
mkdir -p $TMPDIR || {
echo "$0 ABORT: unable to create filelist cache";
rm /var/lock/SSANotifier; exit 1;
}
}
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
}
# define pending upgrades
PENDINGPATCHES=""
export PENDINGPATCHES
# define an empty list of new patches to be downloaded
NEWPATCHES=""
export NEWPATCHES
echo $NEWPATCHES
#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?...)
OBSOLETEPATCHES=""
export OBSOLETEPATCHES
[ ! -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)
GetPending
}
#if we haven't a new list and there is no pending upgrade this means that we can exit
[ -z "$NEWPATCHES" ] && [ -z "$PENDINGPATCHES" ] && {
echo "$0 ABORT: nothing changed";
rm $NEWLIST;
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:" >> $TMPFILE
for FILE in $PENDINGPATCHES; do
echo $(basename $FILE) >> $TMPFILE
done
[ ! $NOTIFY = "GNOME" ] && $DIALOGBASE --title "$TITLE" --textbox "$TMPFILE" $SIZE1 || \
$DIALOGBASE --title "$TITLE" --text-info --filename="$TMPFILE" $SIZE1
rm "$TMPFILE"
[ ! $NOTIFY = "GNOME" ] && {
$DIALOGBASE --title "$TITLE" --yesno "$UMESSAGE" $SIZE2
} #|| { ###this cause a strange behaviour... we have to postpone this feature for other versions...
# $DIALOGBASE --title "$TITLE" --question --text="$UMESSAGE" $SIZE2
#}
[ $? -eq 1 ] && { rm $NEWLIST; 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;
Script SSAUpdater
#! /bin/sh
#
#SSAUpdater: a tool for automatic downloading and installing 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.
#
#!!! !!!
#!!! don't run this script as stand alone application !!!
#!!! SSAUpdater has been designed to be called by SSANotifier !!!
#!!! !!!
#
#v.0.90.0
#
#to do:
#-prova gdialog
#-prova xdialog
#--------------------------------------------------
#general settings
TMPDIR=/tmp/SSANotifier
CONFDIR="/etc/SSANotifier"
#CONFDIR="/home/utente/slack/scripting/SSANotifier/devel" #just for debugging
CONFFILE=$CONFDIR/"SSANotifier.conf"
PATCHLIST="FILE_LIST"
MD5LIST="CHECKSUMS.md5"
#--------------------------------------------------
#--------------------------------------------------
#"resource table" with some messages displayed on screen
TITLE="SSAUpdater"
UMESSAGE="Upgrade the system?"
DMESSAGE="Currently downloading:"
FMESSAGE="Upgrading the system"
GMESSAGE="please wait..."
#--------------------------------------------------
#--------------------------------------------------
#auxiliary functions
function RemoveOldVersion () {
##
## this function remove any old version of a patchd
## it is called olny after a new patch version has been checked, so we can be _REALLY_ sure
## that the file is no longer required
##
for FILE in $GPGPATCHES; do
#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 an old version installed in the system
OLDVERSIONLIST=$(echo $OBSOLETEPATCHES | 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 $OLDVERSIONLIST; 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" ] && {
#remove it
rm $nFILE &>/dev/null
continue
}
done
done
}
#--------------------------------------------------
#--------------------------------------------------
#entry point
#load settings
. $CONFFILE
#if the local mirror doesn't exist we create it!
[ -d $LOCALMIRROR ] || {
mkdir -p $LOCALMIRROR || { echo "$0 ABORT: unable to create local mirror"; exit 1; }
}
#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="500 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="-u root -w"
PASSIVE="--progress --pulsate --auto-close"
SIZE1="500 300"
SIZE2=""
SIZE3="800 600"
SIZE4="200"
SIZE5="1000"
;;
*)
echo "$0 ABORT: selected notification not supported"
exit 1
;;
esac
#if we want to check the gpg encription we download the gpgkey file
#!!!
#!!! useful to check for its existance and set a flag to force the key download every time
#!!!
[ $CHECKGPG -eq 1 ] && {
[ -f $(basename $GPGKEY) ] && rm $(basename $GPGKEY) #force old key remotion.
$WGET $WGETOPTS $GPGKEY || { echo "$0 ABORT: unable to download gpg key"; exit 1; }
#we import the key in our keyring
[ $($GPG --import $(basename $GPGKEY) 1> /dev/null) ] && { echo "$0 ABORT: unable to import gpg key"; exit 1; }
}
#if we want to check the md5sum we download the checksum file
[ $CHECKMD5 -eq 1 ] && {
[ -f "$MD5LIST" ] && rm "$MD5LIST"
$WGET $WGETOPTS $SERVER/"$MD5LIST" || { echo "$0 ABORT: unable to download md5sums"; exit 1; }
[ $CHECKGPG -eq 1 ] && {
#if we want to check the gpg key we download the checksum asc file...
rm "$MD5LIST"".asc" 2> /dev/null
$WGET $WGETOPTS $SERVER/"$MD5LIST"".asc" || { echo "$0 ABORT: unable to download md5sums asc"; exit 1; }
#...and we test it
[ $($GPG --verify $MD5LIST".asc" $MD5LIST 1> /dev/null) ] && \
{ echo "$0 ABORT: md5sums didn't pass gpg check"; exit 1; }
}
}
#echo ${NEWPATCHES}
#create a log file
TMPLOG=$TMPDIR/message-$(date +%Y%m%d%H%M%S)
echo "Upgrade report:" > $TMPLOG
#if we have a new patch we download it
WGETPATCHES=""
for FILE in $NEWPATCHES; do
$DIALOGBASE --title "$DMESSAGE" $PASSIVE $(basename $FILE) $SIZE4 2>/dev/null &
$WGET $WGETOPTS $SERVER/$FILE
[ $? -eq 0 ] || {
rm $(basename $FILE) &>/dev/null #if we fail and we have a partial download we remove the file
echo "unable to download $(basename $FILE): file discarded" >> $TMPLOG
#this line kills the kde popup dialog if it is used... otherwise it does nothing!
kill $(ps aux | grep -m1 "passivepopup" | awk '{printf $2"\n"}') &>/dev/null
continue
}
#if we want to check the gpg key we download tgz signatures
[ $CHECKGPG -eq 1 ] && {
$WGET $WGETOPTS $SERVER/$FILE."asc"
[ $? -eq 0 ] || {
rm $(basename $FILE) &>/dev/null #if we fail we remove the file
rm $(basename $FILE)".asc" &>/dev/null
echo "unable to download $(basename $FILE).asc: file discarded" >> $TMPLOG
kill $(ps aux | grep -m1 "passivepopup" | awk '{printf $2"\n"}') &>/dev/null
continue
}
}
WGETPATCHES="$WGETPATCHES $(basename $FILE)" #if everything goes right we add the file to the upgrade list
kill $(ps aux | grep -m1 "passivepopup" | awk '{printf $2"\n"}') &>/dev/null
done
#echo $WGETPATCHES
[ -z "$WGETPATCHES" ] && [ -z "$PENDINGPATCHES" ] && { echo "$0 ABORT: unable to download patches"; \
rm $NEWLIST; exit 0; }
#if we want to check the md5 it is time to do it!
MD5PATCHES=""
[ $CHECKMD5 -eq 1 ] && {
for FILE in $WGETPATCHES; do
SUM=$($MD5SUM $FILE)
STRING=$(grep $SUM $MD5LIST | cut -f 3 -d /) #if we match a checksum look if it is related to the right file!
[ $STRING==$FILE ] && MD5PATCHES="$MD5PATCHES $FILE" \
|| {
rm $FILE 2> /dev/null #if the file fails the checksum we remove it
rm $FILE".asc" 2>/dev/null
echo "$(basename $FILE) didn't pass the md5sum: file discarded" >> $TMPLOG
}
done
} || { #if we don't want to do any check
MD5PATCHES="$WGETPATCHES"
}
#echo $MD5PATCHES
[ -z "$MD5PATCHES" ] && [ -z "$PENDINGPATCHES" ] && { echo "$0 ABORT: no patch passed the md5 check"; \
rm $NEWLIST; exit 0; }
#if we want to check gpg key
[ $CHECKGPG -eq 1 ] && {
for FILE in $MD5PATCHES; do
$GPG --verify $FILE".asc" $FILE 1>/dev/null
[ ! $? -eq 0 ] && {
rm $FILE 2> /dev/null #if the file fails the gpg check we remove it
rm $FILE".asc" 2>/dev/null
echo "$(basename $FILE) didn't pass the gpg check: file discarded" >> $TMPLOG
} || {
GPGPATCHES="$GPGPATCHES $FILE" #esle we add the file to the upgrade list
}
done
} || { #else we skip the gpg signature check
GPGPATCHES="$MD5PATCHES"
}
#append pending patches (they have been checked in a previous session)
GPGPATCHES="$GPGPATCHES $PENDINGPATCHES"
[ -z "$GPGPATCHES" ] && { echo "$0 ABORT: no patch passed the gpg check"; rm $NEWLIST; exit 0; }
#if and old version was present in the local mirror, we remove it! (we use the OBSOLETEPATCHES variable)
RemoveOldVersion
$UPGRADEPKG $UPOPTS $GPGPATCHES >> $TMPLOG
#show report
[ ! $NOTIFY = "GNOME" ] && $DIALOGBASE --title "$TITLE" --textbox "$TMPLOG" $SIZE3 || \
$DIALOGBASE --title "$TITLE" --text-info --filename="$LOCALMIRROR/$TMPLOG" $SIZE3
rm $TMPLOG
# update info about last check
mv $NEWLIST $OLDLIST 2>/dev/null
SSANotifier.conf
####################################################################### #set your download options (see wget man page). #leave it black for no options. by default the -q option is used WGETOPTS="-q" ####################################################################### #set to 1 if you want to check the md5 of tgzs. set to 0 otherwise #!!! note that the md5sum utility is still required, even if you set this value to 0 !!! CHECKMD5=1 ####################################################################### #set to 1 if you want to check the gpg key of tgzs. set to 0 otherwise CHECKGPG=1 ####################################################################### #set location of slackware public gpg key #!!! used only if CHECKGPG is set to 1 !!! #for example: #GPGKEY=http://slackware.osuosl.org/slackware-12.0/GPG-KEY GPGKEY= ####################################################################### #select your preferred patches server. for example: #SERVER="http://slackware.osuosl.org/slackware-12.0/patches" SERVER= ####################################################################### #select your preferred location to store patches. #for example: #LOCALMIRROR="/var/log/patches/" LOCALMIRROR= ####################################################################### #set your preferred way for notifications #possible values are: #NOTIFY=KDE to use from KDE via the kdialog interface #NOTIFY=SHELL to use from shell via the dialog interface #NOTIFY=GNOME !!! actually not implemented !!! to use from GNOME via the gdialog interface, actually not implemented #NOTIFY=X11 !!! actually not implemented !!! to use with any grafical environment different from kde and gnome NOTIFY=SHELL ####################################################################### #set your upgrade command (full path). for example: #UPGRADEPKG=/sbin/upgradepkg UPGRADEPKG=/sbin/upgradepkg ####################################################################### #set upgrade options (see man page of your upgrade utility) #leave it black for no options. #!!! if you want to test the tool behaviour without any real update, !!! #!!! try the /sbin/upgradepkg utility and use the --dry-run option !!! UPOPTS="--dry-run"
- Data: 21/10/2007
- Autore: Absinthe