#!/bin/bash
#
# Datenbank-Wartung.
#

function info
{
  echo -e '
  SYNTAX: tw_service [-c ext] [-u Obj1,Obj2,...] [-UbBrRfiId]

  -c  Wählt die Konfiguration "tw.config-ext" an.
  -u  Aktualisiert zugehörige Datenbankeinträge.
  -U  Frontend für Abstimmungsarbeiten an der Schildkonfiguration.
  -b  Sichert die mit BACKUP markierten Objekte.
      ([!|=] Objekt [Auswahlmaske] [#Kommentar [BACKUP]])
  -B  Wie "-b" aber mit Menü und anschließendem Update. (st_atime-Problem)
  -r  Rückübertragen des Archivinhalts ins System.
  -R  Wie "-r" aber mit Menü und anschließendem Update. (st_ino, st_ctime, ...)
  -f  Schaltet Überschreibschutz für jüngere Systemdateien aus. (Vorsicht!)
  -i  Interaktive Aktualisierung der betreffenden Datenbank.
  -I  Wie "-i" aber mit Menü zur Datenbankanwahl.
  -d  Restauriert die Datenbank.\n'
}

. ${0%/*}/tw_conf ; . ${0%/*}/tw_lib

cf=$DESTDIR/tw.config
db=$DATADIR/$DATABASE
dbsig=$DBSIGDIR/$DBSIG
dbback=$BACKUPDIR/$DATABASE

function error
{
  echo "--> ${0##*/}: $*" ; exit 1
}

function check_config
{
  if ! [ -e $cf$ext ] || ! [ -e $db$ext ] || ! [ -e $dbsig$ext ]; then
    error 'Keine gültige Konfiguration!'
  elif [ "$(< $dbsig$ext)" != "$($DESTDIR/siggen -7 $db$ext)" ]; then
    error 'Datenbank manipuliert!'
  fi
}

function go
{
  # Update!
  local new_db="./databases/$DATABASE" \
  conf="--cfgfile $cf$ext --dbfile $db$ext" end
  check_config
  [ -e $new_db ] && mv $new_db $new_db.tmp
  $DESTDIR/tripwire --quiet ${ext:+$conf} $* >/dev/null 2>&1
  if [ $? = 1 ]; then
    [ -e $new_db.tmp ] && mv $new_db.tmp $new_db
    error 'Laufzeitfehler beim Update!'
  fi

  # Datenbank installieren!
  if [ -e $new_db ]; then
    cp -v $new_db $db$ext
    # Arbeitskopie...
    $DESTDIR/siggen -7 $new_db >$dbsig$ext
    #...zug. Signatur...
    gzip -1 -c $new_db >$dbback$ext.gz ; gzip -t $dbback$ext.gz
    # ...und Sicherheitskopie der Datenbank.
    chmod 600 $db$ext $dbsig$ext $dbback$ext.gz
    end=".old"
  fi
  [ -e $new_db.tmp ] && mv $new_db.tmp $new_db$end
}

function reconfigure
{
  function strip
  {
    sed "s/[$SEP]*#.*$//g;s/[$SEP]\+/ /g" $1
  }

  # Konfiguration berichtigen!
  local conf=$cf$ext path news
  ! [ -e $conf ] && error 'Kann Konfigurationsdatei nicht finden!'
  cp $conf $conf.old ; $EDITCOM $conf
  [ $conf.old -nt $conf ] && { rm -f $conf.old; return; }

  # Neues?
  declare -i n=0 m=0
  for path in $(diff <(strip $conf.old) <(strip $conf) | grep '^[<>]' | \
  sed "s/^..[\=\!]\?//g;s/[$SEP].*$//g" | sort -u); do
    if grep -q "^$path.*#.*FLAG" $conf; then
      troj_path[$n]=$path ; n=$((n+1))
    else
      main_path[$m]=$path ; m=$((m+1))
    fi
    news=yes
  done
  [ -z "$news" ] && { rm -f $conf.old; return; }

  # Update!
  if scat_dummies "${troj_path[@]}"; then
    rm -f $conf.old
    go --update ${main_path[*]} ${troj_path[*]}
  else
    error "Kein Schreibrecht in $scat_dir!"
  fi
  rm -f ${troj_path[*]}
}

function backup
{
  # Backup-Volumen integer?
  local conf=$cf$ext type=${ext#-} int_sec arch=$BACKUPDIR/$ARCHIVES$ext.gz
  ! [ -e $conf ] && error 'Kann Konfigurationsdatei nicht finden!'
  set_entry BACKUP <$conf || error 'Keine Einträge zur Datensicherung markiert!'
  $DESTDIR/tw_check ${type:+-c $type} >/dev/null 2>&1
  [ $? = 1 ] && error 'Laufzeitfehler beim Integritätstest!'
  tail +"$(grep -n 'Folgende Ver' $REPORT | tail -1 | sed -e 's/:.*//g')" $REPORT | \
  sed '/^[^adct]/d;/^$/d' >$REPORT.new
  int_sec=$(for path in ${entry[*]}; do echo "$(grep $path $REPORT.new)"; done)
  if [ -n "$int_sec" ]; then
    rm -f $REPORT.new
    echo -e "--> ${0##*/}: Backup abgebrochen, da Inkonsistenzen vorhanden!\n$int_sec" | \
    sed '/^$/d;s/^\([adct].*\)$/    -> \1/g' | less
    exit 1
  fi
  rm -f $REPORT.new

  # Backup!
  eval find ${entry[*]} | cpio -oV -H crc -M 'Kapazität des Backup-Mediums %d erschöpft!' | \
  gzip >$arch
  # Erzeugt ein SVR4-Archiv in BACKUPDIR.
  gzip -t $arch ; chmod 600 $arch

  # Update zur st_atime-Korrektur?
  if [ "$1" ]; then
    go --update ${entry[*]}
  fi
}

function restore
{
  # Archiv vorhanden?
  local arch=$BACKUPDIR/$ARCHIVES$ext.gz cp_cmd vol=/tmp/v.$$ list
  ! [ -e $arch ] && error 'Kann Archiv nicht finden!'

  # Rückübertragen!
  if [ -z "$replace_mode" ]; then
    cp_cmd='cpio -imv 2>&1'
  else
    cp_cmd='cpio -imvu 2>&1'
    # Dieses Kommando überschreibt auch jüngere Dateien und kann deshalb benutzt werden, um
    # manipulierte Systemdateien etwa nach einer Kompromittierung des Rechners unschädlich zu
    # machen. Unglücklicherweise gehen dabei auch frische Veränderungen administrativer Art
    # verloren. Um die Vorzüge des "restore"-Mechanismus voll ausschöpfen zu können, ist es daher
    # erforderlich, Korrekturen an der Systemkonfiguration von Zeit zu Zeit mit "tw_service -B"
    # ins Backup-Volumen zu übernehmen!
  fi
  gzip -d -c $arch | eval $cp_cmd | tee $vol | grep '^/' | sed 's/^.*$/\./g' | tr -dc '.'
  echo ; grep '^[0-9]' $vol

  # Update korrespondierender Datenbankeinträge?
  if [ "$1" ]; then
    list=$(grep '^/' $vol)
    [ -n "$list" ] && go --update $list
  fi
  rm -f $vol
}

function restore_db
{
  local local_db=$db$ext new_db=$dbback$ext.gz
  ! [ -e $new_db ] && error 'Kann Sicherheitskopie der Datenbank nicht finden!'
  [ -e $local_db ] && mv $local_db $local_db.dead
  gzip -d -c $new_db >$local_db
  chmod 600 $local_db
}

function ask_op
{
  function set_ext
  {
    ext="-$1"
    [ $1 = $DEFDBNAME ] && unset ext
  }

  local list=$(databases) cfg
  declare -i num_db=$(echo $list | wc -w | tr -d ' ')
  case $num_db in
    0) error 'Keine Konfigurationsdatei!';;
    1) set_ext $list ; eval "$*";;
    *) PS3='Datenbank? '
       select cfg in $list; do
         if [ "$cfg" ]; then
           set_ext $cfg ; eval "$*"
           read -p 'Abbrechen? (j/n) '
           [ "$REPLY" != 'n' ] && exit 0
         fi
       done;;
  esac
} 

# Anweisungsteil

if [ -z "$*" ]; then
  info ; exit 1
fi

while getopts "c:fu:UbBrRiIdh" opt; do
  case $opt in
    c) ext="-$OPTARG";;
    f) replace_mode=true;;
    u) cmd_obj=${OPTARG//,/ } ; cmd=upd;;
    U) cmd=Upd;;
    b) cmd=back;;
    B) cmd=Back;;
    r) cmd=rest;;
    R) cmd=Rest;;
    i) cmd=intact;;
    I) cmd=Intact;;
    d) cmd=rest_db;;
    h) info ; exit 0;;
    *) exit 1;;
  esac
done

case $cmd in
  upd    ) go --update $cmd_obj;;
  Upd    ) ask_op 'reconfigure';;
  back   ) backup;;
  Back   ) ask_op 'backup and_update';;
  rest   ) restore;;
  Rest   ) ask_op 'restore and_update';;
  intact ) go --interactive;;
  Intact ) ask_op 'go --interactive';;
  rest_db) restore_db;;
esac

