#!/bin/sh
#
# greenbone-scap-sync
# This script synchronizes an OpenVAS installation with the Greenbone SCAP
# data directory.
#
# Authors:
# Henri Doreau <henri.doreau@greenbone.net>
# Michael Wiegand <michael.wiegand@greenbone.net>
# Timo Pollmeier <timo.pollmeier@greenbone.net>
#
# Copyright:
# Copyright (C) 2011, 2012, 2013 Greenbone Networks GmbH
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2,
# or, at your option, any later version as published by the Free
# Software Foundation
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA.

VERSION=20130514
RESTRICTED=1

# SETTINGS
# ========

# Path prefix for installation location
PREFIX=

# PRIVATE_SUBDIR defines a subdirectory of the CERT data directory
# where files not part of the feed or database will not be deleted by rsync.
if [ -z "$PRIVATE_SUBDIR" ]
then
  PRIVATE_SUBDIR="private"
fi

# RSYNC_DELETE controls whether files which are not part of the repository will
# be removed from the local directory after synchronization. The default value
# for this setting is
# "--delete --exclude scap.db --exclude $PRIVATE_SUBDIR/",
# which means that files which are not part of the feed, database or private
# directory will be deleted.
RSYNC_DELETE="--delete --exclude scap.db --exclude $PRIVATE_SUBDIR/"

# RSYNC_SSH_OPTS contains options which should be passed to ssh for the rsync
# connection to the repository.
RSYNC_SSH_OPTS="-o \"UserKnownHostsFile=/dev/null\" -o \"StrictHostKeyChecking=no\""

# RSYNC_COMPRESS specifies the compression level to use for the rsync connection.
RSYNC_COMPRESS="--compress-level=9"

# PORT controls the outgoing TCP port for updates. If PAT/Port-Translation is
# not used, this should be "24". For some application layer firewalls or gates
# the value 22 (Standard SSH) is useful. Only change if you know what you are
# doing.
PORT=24

# If ENABLED is set to 0, the sync script will not perform a synchronization.
ENABLED=1

# If REFRESH_ONLY is set to 1, the sync script will only update the OpenVAS
# Manager database. This can be controlled via the --refresh parameter.
REFRESH_ONLY=0
# If REFRESH_PRIVATE_ONLY is set to 1, the sync script will only update
# private data in the OpenVAS Manager database.
# This can be controlled via the --refresh-private parameter.
REFRESH_PRIVATE_ONLY=0
# When set to 1, all OVAL definitions will be updated, not just new ones.
# This is usually done by database migrations.
REBUILD_OVAL=0
# Setting this to 1 enables verbose mode, which outputs additional data.
# This is usually done with the option --verbose
VERBOSE=0

# SQLITE3 defines the name of the sqlite binary to call, along with additional
# parameters.
SQLITE3="sqlite3 -noheader"

# SCRIPT_NAME is the name the scripts will use to identify itself and to mark
# log messages.
SCRIPT_NAME="greenbone-scapdata-sync"

# LOG_CMD defines the command to use for logging. To have logger log to stderr
# as well as syslog, add "-s" here.
LOG_CMD="logger -t $SCRIPT_NAME"

[ -r $PREFIX/etc/openvas/greenbone-scapdata-sync.conf ] && . $PREFIX/etc/openvas/greenbone-scapdata-sync.conf

BASE_SYNC_DIR="$PREFIX/var/lib/openvas"
SCAP_DIR="$BASE_SYNC_DIR/scap-data"

if [ -z "$PREFIX" ] ; then
  SCAP_RES_DIR="/usr/share/openvas/scap"
else
  SCAP_RES_DIR="$PREFIX/share/openvas/scap"
fi

SCAP_DB="$SCAP_DIR/scap.db"

ACCESSKEY="$PREFIX/etc/openvas/gsf-access-key"

TIMESTAMP="$SCAP_DIR/timestamp"

if [ -z "$FEED_NAME" ] ; then
  FEED_NAME="Greenbone SCAP Feed"
fi

if [ -z "$FEED_VENDOR" ] ; then
  FEED_VENDOR="Greenbone Networks GmbH"
fi

if [ -z "$FEED_HOME" ] ; then
  FEED_HOME="http://www.greenbone.net/solutions/gbn_feed.html"
fi

RSYNC=`command -v rsync`
SQLITE=`command -v sqlite3`

do_describe () {
  echo "This script synchronizes a SCAP collection with the '$FEED_NAME'."
  echo "The '$FEED_NAME' is provided by '$FEED_VENDOR'."
  echo "Online information about this feed: '$FEED_HOME'."
}

do_feedversion () {
  if [ -r $TIMESTAMP ] ; then
      echo `cat $TIMESTAMP`
  fi
}

log_debug () {
  $LOG_CMD -p daemon.debug $1
}

log_info () {
  $LOG_CMD -p daemon.info $1
}

log_notice () {
  $LOG_CMD -p daemon.notice $1
}

log_warning () {
  $LOG_CMD -p daemon.warning $1
}

log_err () {
  $LOG_CMD -p daemon.err $1
}

is_feed_current () {
  if [ -r $TIMESTAMP ]
  then
    FEED_VERSION=`cat $TIMESTAMP`
  fi

  if [ -z "$FEED_VERSION" ]
  then
    log_warning "Could not determine feed version."
    FEED_CURRENT=0
    return $FEED_CURRENT
  fi

  FEED_INFO_TEMP_DIR=`mktemp -d`

  if [ -e $ACCESSKEY ]
  then
    read feeduser < $ACCESSKEY
    custid=`head -1 $ACCESSKEY | cut -d @ -f 1`
    if [ -z $feeduser ] || [ -z $custid ]
    then
      log_err "Could not determine credentials, aborting synchronization."
      rm -rf $FEED_INFO_TEMP_DIR
      exit -1
    fi

    # --protocol=29 is a workaround for a known bug in rsync 3.0.3
    if [ -e /admin/ezcli.state ]
    then
      gsmproxy=`grep proxy_feed /admin/ezcli.state | sed -e 's/^.*\/\///' -e 's/:/ /' -e 's/[\t ]*$//'`
      PORT=`grep ^syncport /admin/ezcli.state | sed -e "s/^syncport\t//g"`
    fi
    if [ -z "$gsmproxy" ] || [ "$gsmproxy" = "proxy_feed" ]
    then
      RSYNC_SSH_PROXY_CMD=""
    else
      if [ -r /admin/proxyauth ] && [ -s /admin/proxyauth ]; then
        RSYNC_SSH_PROXY_CMD="-o \"ProxyCommand corkscrew $gsmproxy %h %p /admin/proxyauth\""
      else
        RSYNC_SSH_PROXY_CMD="-o \"ProxyCommand corkscrew $gsmproxy %h %p\""
      fi
    fi
    rsync -e "ssh $RSYNC_SSH_OPTS $RSYNC_SSH_PROXY_CMD -p $PORT -i $ACCESSKEY" -ltvrP --protocol=29 --chmod=D+x $RSYNC_DELETE $RSYNC_COMPRESS $custid@feed.greenbone.net:/scap-data/timestamp $FEED_INFO_TEMP_DIR
    if [ $? -ne 0 ]
    then
      log_err "rsync failed, aborting synchronization."
      rm -rf $FEED_INFO_TEMP_DIR
      exit -1
    fi
  else
    log_err "Could not find access key, aborting synchronization."
    rm -rf $FEED_INFO_TEMP_DIR
    exit -1
  fi

  FEED_VERSION_SERVER=`cat $FEED_INFO_TEMP_DIR/timestamp`

  if [ -z "$FEED_VERSION_SERVER" ]
  then
    log_err "Could not determine server feed version."
    rm -rf $FEED_INFO_TEMP_DIR
    exit -1
  fi

  # Check against FEED_VERSION
  if [ $FEED_VERSION -lt $FEED_VERSION_SERVER ] ; then
    FEED_CURRENT=0
  else
    FEED_CURRENT=1
  fi

  # Cleanup
  rm -rf $FEED_INFO_TEMP_DIR

  return $FEED_CURRENT
}

if [ $ENABLED -ne 1 ]
then
  log_notice "SCAP synchronization is disabled, exiting."
  exit 0
fi

is_db_broken () {
  DB_BROKEN=0
  if [ ! -e $SCAP_DB ]
  then
    return
  fi
  DB_INTEGRITY=`sqlite3 $SCAP_DB "PRAGMA integrity_check;"`
  if [ "$DB_INTEGRITY" != "ok" ]
  then
    DB_BROKEN=1
    log_warning "Database integrity check failed."
  fi
}

reinit () {
  log_notice "Reinitialization of the database necessary."
  rm -f $SCAP_DB
  $SQLITE3 $SCAP_DB < $SCAP_RES_DIR/scap_db_init.sql
}

db_migrate_9 () {
  log_notice "Migrating database to version 9."
  $SQLITE3 "$SCAP_DB" "DROP INDEX IF EXISTS afp_idx;
                       CREATE INDEX afp_cpe_idx ON affected_products (cpe);
                       CREATE INDEX afp_cve_idx ON affected_products (cve);
                       UPDATE meta SET value = '9' WHERE name = 'database_version';"
  log_notice "Migration done."
}

db_migrate_10 () {
  log_notice "Migrating database to version 10."
  $SQLITE3 "$SCAP_DB" "DROP TRIGGER affected_delete;
                       CREATE TRIGGER affected_delete AFTER DELETE ON affected_products
                       BEGIN
                         UPDATE cpes set max_cvss = 0.0 WHERE id = old.cpe;
                         UPDATE cpes set cve_refs = cve_refs -1 WHERE id = old.cpe;
                       END;
                       UPDATE meta
                         SET value = '10'
                         WHERE name = 'database_version';"
  log_notice "Migration done."
}

db_migrate_11 () {
  log_notice "Migrating database to version 11."
  if [ 1 != "$REFRESH_PRIVATE_ONLY" ]
  then
    sqlite3 "$SCAP_DB"  "ALTER TABLE ovaldefs
                          ADD COLUMN status TEXT;
                        UPDATE meta
                          SET value = '11'
                          WHERE name = 'database_version';
                      "
    REBUILD_OVAL=1
    log_notice "Migration done."
  else
    log_err "Migration to version 11 requires refresh of all SCAP data. Aborting."
    exit 1
  fi
}

db_migrate_12 () {
  log_notice "Migrating database to version 12."
  if [ 1 != "$REFRESH_PRIVATE_ONLY" ]
  then
    sqlite3 "$SCAP_DB" "DROP TABLE ovaldefs;
                        DROP INDEX IF EXISTS ovaldefs_idx;
                        CREATE TABLE ovaldefs (
                          id INTEGER PRIMARY KEY AUTOINCREMENT,
                          uuid UNIQUE,
                          name,
                          comment,
                          creation_time DATE,
                          modification_time DATE,
                          version INTEGER,
                          deprecated BOOLEAN,
                          def_class TEXT,
                          title TEXT,
                          description TEXT,
                          xml_file TEXT,
                          status TEXT
                        );
                        CREATE INDEX ovaldefs_idx ON ovaldefs (name);
                        CREATE TABLE ovalfiles (
                          id INTEGER PRIMARY KEY AUTOINCREMENT,
                          xml_file TEXT UNIQUE
                        );
                        CREATE UNIQUE INDEX ovalfiles_idx ON ovalfiles (xml_file);
                        CREATE TRIGGER ovalfiles_delete AFTER DELETE ON ovalfiles
                        BEGIN
                          DELETE FROM ovaldefs WHERE ovaldefs.xml_file = old.xml_file;
                        END;
                        UPDATE meta
                          SET value = '12'
                          WHERE name = 'database_version';
                      "
    REBUILD_OVAL=1
    log_notice "Migration done."
  else
    log_err "Migration to version 12 requires refresh of all SCAP data. Aborting."
    exit 1
  fi
}

db_migrate_13 () {
  log_notice "Migrating database to version 13."
  sqlite3 "$SCAP_DB"  "ALTER TABLE cpes ADD COLUMN nvd_id;
                      UPDATE cpes SET nvd_id = uuid, uuid = name;
                      UPDATE meta
                        SET value = '13'
                        WHERE name = 'database_version';
                     "
  log_notice "Migration done."
}

db_migrate_14 () {
  log_notice "Migrating database to version 14."
  sqlite3 "$SCAP_DB" "ALTER TABLE ovaldefs ADD COLUMN max_cvss FLOAT;
                      ALTER TABLE ovaldefs ADD COLUMN cve_refs INTEGER;

                      CREATE TABLE affected_ovaldefs (
                        cve INTEGER NOT NULL,
                        ovaldef INTEGER NOT NULL,
                        FOREIGN KEY(cve) REFERENCES cves(id),
                        FOREIGN KEY(ovaldef) REFERENCES ovaldefs(id)
                      );
                      CREATE INDEX aff_ovaldefs_def_idx
                        ON affected_ovaldefs (ovaldef);
                      CREATE INDEX aff_ovaldefs_cve_idx
                        ON affected_ovaldefs (cve);

                      DROP TRIGGER IF EXISTS cves_delete;
                      CREATE TRIGGER cves_delete AFTER DELETE ON cves
                      BEGIN
                        DELETE FROM affected_products where cve = old.id;
                        DELETE FROM affected_ovaldefs where cve = old.id;
                      END;

                      CREATE TRIGGER affected_ovaldefs_delete AFTER DELETE
                        ON affected_ovaldefs
                      BEGIN
                        UPDATE ovaldefs
                          SET max_cvss = 0.0
                          WHERE id = old.ovaldef;
                      END;

                      UPDATE meta
                        SET value = '14'
                        WHERE name = 'database_version';
                     "
  log_notice "Migration done."
  REBUILD_OVAL=1
}

check_db_version () {
  DB_VERSION=`$SQLITE3 $SCAP_DB "select value from meta where name = 'database_version';" 2>/dev/null || echo 0`
  case "$DB_VERSION" in
    0) reinit;;
    1) reinit;;
    2) reinit;;
    3) reinit;;
    4) reinit;;
    5) reinit;;
    6) reinit;;
    7) reinit;;
    8) db_migrate_9
       db_migrate_10
       db_migrate_11
       db_migrate_12
       db_migrate_13
       db_migrate_14;;
    9) db_migrate_10
       db_migrate_11
       db_migrate_12
       db_migrate_13
       db_migrate_14;;
   10) db_migrate_11
       db_migrate_12
       db_migrate_13
       db_migrate_14;;
   11) db_migrate_12
       db_migrate_13
       db_migrate_14;;
   12) db_migrate_13
       db_migrate_14;;
   13) db_migrate_14;;
  esac
}

list_oval_files_sorted () {
for line in `list_oval_files_sortable "$1" | sort`
  do
    echo ${line#????????}
  done
}

list_oval_files_sortable () {
for ovalfile in `find "$1" -name "*.xml" -type f`
  do
    timestamp_raw=""
    timestamp_raw=`xsltproc "$SCAP_RES_DIR/oval_timestamp.xsl" "$ovalfile" 2>/dev/null`

    if [ -n "$timestamp_raw" ] \
       && [ -n `echo "$timestamp_raw" | egrep -x "[0-9]{4}\-(0[0-9]|1[012])\-([0-2][0-9]|3[01])[[:alnum:]]*"` ]
    then
      timestamp="`echo "$timestamp_raw" | sed -e 's/T.*//' | tr -d "-"`"
      echo "$timestamp$ovalfile"
    fi
  done
}

do_help () {
  echo "$0: Sync SCAP data"
  echo " --describe        display current feed info"
  echo " --feedversion     display version of this feed"
  echo " --help            display this help"
  echo " --identify        display information"
  echo " --refresh         update database without downloading new state"
  echo " --refresh-private update database using only user data"
  echo " --selftest        perform self-test"
  echo " --version         display version"
  echo " --verbose         enable verbose log messages"
  echo ""
  exit 0
}

init_scap_db_update () {
  if [ ! -f $SCAP_DB ]
  then
    log_notice "Initializing SCAP database."
    $SQLITE3 $SCAP_DB < $SCAP_RES_DIR/scap_db_init.sql
    CVE_REFDATE=0
  else
    check_db_version
    CVE_REFDATE=`$SQLITE3 $SCAP_DB "select date(max(modification_time),'unixepoch') from cves;" | sed -e 's/T.*//' | tr -d "-"`
  fi

  DB_LASTUPDATE=`$SQLITE3 $SCAP_DB "select value from meta where name = 'last_update';"`

  if [ -z "$CVE_REFDATE" ]
  then
    CVE_REFDATE=0
  fi

  if [ -z "$DB_LASTUPDATE" ];
  then
    # Happens when initial sync was aborted
    log_warning "Inconsistent data. Resetting SCAP database."
    rm -f $SCAP_DB
    $SQLITE3 $SCAP_DB < $SCAP_RES_DIR/scap_db_init.sql
    CVE_REFDATE=0
    DB_LASTUPDATE=0
  fi

  updated_cpes=0
  updated_cves=0
  updated_ovaldefs=0
}

update_cvss () {
  if [ 0 -ne $updated_cves ] || [ 0 -ne $updated_cpes ]
  then
    log_info "Updating CVSS scores and CVE counts for CPEs"
    $SQLITE3 "$SCAP_DB"  "
    PRAGMA recursive_triggers = OFF;
    UPDATE cpes
      SET max_cvss =
          (
            SELECT coalesce(max(cvss), '')
            FROM cves
            WHERE id IN
              (
                SELECT cve
                FROM affected_products
                WHERE cpe=cpes.id
              )
          ),
        cve_refs =
          (
            SELECT count(cve)
            FROM affected_products
            WHERE cpe=cpes.id
          );
    "
  else
    log_info "No CPEs or CVEs updated, skipping CVSS and CVE recount for CPEs."
  fi

  if [ 0 -ne $updated_cves ] || [ 0 -ne $updated_ovaldefs ]
  then
    log_info "Updating CVSS scores and for OVAL definitions"
    $SQLITE3 "$SCAP_DB"  "
    PRAGMA recursive_triggers = OFF;
    UPDATE ovaldefs
      SET max_cvss =
          (
            SELECT coalesce(max(cvss), '')
            FROM cves
            WHERE id IN
              (
                SELECT cve
                FROM affected_ovaldefs
                WHERE ovaldef=ovaldefs.id
              )
              AND cvss != ''
          );
    "
  else
    log_notice "No OVAL definitions or CVEs updated, skipping CVSS recount for OVAL definitions."
  fi
}

update_scap_db_private () {
  SCAP_PRIVATE_DIR="$SCAP_DIR/$PRIVATE_SUBDIR"
  if [ -d "$SCAP_PRIVATE_DIR" ]
  then
    log_notice "Updating user defined data"

    log_info "Updating user OVAL definitions"
    xmlcount=0
    xmlcount=$(find "$SCAP_PRIVATE_DIR/oval/" -name "*.xml" -type f 2> /dev/null | wc -l)
    if [ -n "$xmlcount" ] && [ $xmlcount -ne 0 ]
    then
      if [ -z "$oval_files_sorted" ]
      then
        oval_files_sorted=`list_oval_files_sorted "$SCAP_DIR/oval/"`
        ovaldir_xml_files=`find "$SCAP_DIR/oval/" -name "*.xml" -type f 2> /dev/null`

        for xml_file in $ovaldir_xml_files
        do
          if [ -z `echo "$oval_files_sorted" | grep -x "$xml_file"` ]
          then
            log_warning "Cannot find valid OVAL timestamp in '$xml_file'."
          fi
        done
      fi

      oval_files_sorted_private=`list_oval_files_sorted "$SCAP_PRIVATE_DIR/oval/"`
      ovaldir_xml_files=`find "$SCAP_PRIVATE_DIR/oval/" -name "*.xml" -type f 2> /dev/null`

      for xml_file in $ovaldir_xml_files
      do
        if [ -z `echo "$oval_files_sorted_private" | grep -x "$xml_file"` ]
        then
          log_warning "Cannot find valid OVAL timestamp in '$xml_file'."
        fi
      done

      for non_xml_file in `find "$SCAP_PRIVATE_DIR/oval/" -type f -and -not -name "*.xml" 2> /dev/null`
      do
        if [ "${non_xml_file%%.asc}" = "${non_xml_file}" ] || ! [ -f "${non_xml_file%%.asc}" ]
        then
          log_warning "Found non-XML and non-signature file '$non_xml_file'."
        fi
      done

      if [ -n "$oval_files_sorted" ]
      then
        for ovalfile in $oval_files_sorted_private
        do
          if [ `stat -c "%Y" "$ovalfile" | cut -d " " -f 1 | tr -d "-"` -ge $DB_LASTUPDATE ] || [ 1 = $REBUILD_OVAL ]
          then
            oval_timestamp=`xsltproc "$SCAP_RES_DIR/oval_timestamp.xsl" "$ovalfile" | sed -e 's/T.*//' | tr -d "-"`

            if [ 1 = "$REBUILD_OVAL" ]
            then
              OVAL_REFDATE=0
            else
              OVAL_REFDATE=`$SQLITE3 $SCAP_DB "SELECT date(max(modification_time),'unixepoch') FROM ovaldefs WHERE xml_file = '${ovalfile##$SCAP_DIR/}';" | tr -d "-"`
              if [ -z "$OVAL_REFDATE" ]
              then
                OVAL_REFDATE=0
              fi
            fi

            if [ $oval_timestamp -ge $OVAL_REFDATE ]
            then
              validation_message=`xsltproc "$SCAP_RES_DIR/oval_verify.xsl" "$ovalfile" 2>/dev/null`
              validation_return="$?"
              if [ 0 -ne $validation_return ] && [ -n "$validation_message" ]
              then
                if [ 10 -eq $validation_return ]
                then
                  log_warning "Validation of file '$ovalfile' failed: $validation_message"
                else
                  log_warning "Error parsing '$ovalfile'."
                fi
              else
                if ! [ "$validation_message" = "file valid" ]
                then
                  log_warning "Validation of file '$ovalfile' failed: $validation_message"
                else
                  log_info "Updating $ovalfile"
                  xsltproc --stringparam filename "${ovalfile##$SCAP_DIR/}" --stringparam refdate "$OVAL_REFDATE" "$SCAP_RES_DIR/oval_update.xsl" "$ovalfile" | \
                  $SQLITE3 $SCAP_DB
                  updated_ovaldefs=1
                fi
              fi
            else
              log_info "Skipping $ovalfile, file has older timestamp than latest OVAL definition in database. (This is not an error)"
            fi
          else
            log_info "Skipping $ovalfile, file is older than last revision. (This is not an error)"
          fi
        done
      fi
    else
      log_info "No user defined OVAL files found"
    fi

    log_info "Cleaning up user OVAL data"
    DIR_STR_LENGTH=$((`echo "$SCAP_DIR" | wc -c` + 1))

    oval_files_shortened=""
    if [ 0 != "$xmlcount" ]
    then
      for ovalfile in $oval_files_sorted_private
      do
        oval_files_shortened=$oval_files_shortened"', '"`echo $ovalfile | cut -c ${DIR_STR_LENGTH}- `
      done
      oval_files_shortened=${oval_files_shortened##"', "}"'"
    fi

    deleted=`$SQLITE3 "$SCAP_DB" "SELECT DISTINCT xml_file FROM ovaldefs WHERE (xml_file NOT LIKE 'oval/%') AND (xml_file NOT IN ($oval_files_shortened));"`
    if [ -n "$deleted" ]
    then
      log_notice "Removing definitions formerly inserted from: $deleted"
      $SQLITE3 "$SCAP_DB" "DELETE FROM ovaldefs WHERE (xml_file NOT LIKE 'oval/%') AND (xml_file NOT IN ($oval_files_shortened));"
    fi

    $SQLITE3 $SCAP_DB "update meta set value ='`date +%s`' where name = 'last_update';"
  else
    log_notice "No user data directory '$SCAP_PRIVATE_DIR' found."
  fi
}

update_scap_db() {
  init_scap_db_update

  if [ 0 = "$REFRESH_PRIVATE_ONLY" ]
  then
    log_notice "Updating data from feed"
    CPEBASE="$SCAP_DIR/official-cpe-dictionary_v2.2.xml"
    if [ -e $CPEBASE ]
    then
      if [ `stat -c "%Y" $CPEBASE | cut -d " " -f 1 | tr -d "-"` -ge $DB_LASTUPDATE ]
      then
        log_info "Updating CPEs"
        sed 's/&/&amp;/g' $CPEBASE | \
          xsltproc --stringparam refdate $CVE_REFDATE $SCAP_RES_DIR/cpe_youngerthan.xsl - | \
          xsltproc $SCAP_RES_DIR/cpe_update.xsl - | \
          $SQLITE3 $SCAP_DB
        updated_cpes=1
      else
        log_info "Skipping CPEs, file is older than last revision (this is not an error)."
      fi
    else
      log_warning "No CPE dictionary found in $SCAP_DIR"
    fi

    xmlcount=$(ls $SCAP_DIR/nvdcve-2.0-*.xml 2> /dev/null | wc -l)
    if [ $xmlcount -ne 0 ]
    then
      for cvefile in `ls $SCAP_DIR/nvdcve-2.0-*.xml`
      do
        if [ `stat -c "%Y" $cvefile | cut -d " " -f 1 | tr -d "-"` -ge $DB_LASTUPDATE ]
        then
          updated_cves=1
          log_info "Updating $cvefile"
          xsltproc --stringparam refdate $CVE_REFDATE $SCAP_RES_DIR/cve_youngerthan.xsl $cvefile | \
            xsltproc $SCAP_RES_DIR/cve_update.xsl - | \
            $SQLITE3 $SCAP_DB
        else
          log_info "Skipping $cvefile, file is older than last revision (this is not an error)."
        fi
      done
    else
      log_warning "No CVEs found in $SCAP_DIR."
    fi

    log_info "Updating OVAL data"
    xmlcount=$(find "$SCAP_DIR/oval/" -name "*.xml" -type f 2> /dev/null | wc -l)
    if [ $xmlcount -ne 0 ]
    then
      oval_files_sorted=`list_oval_files_sorted "$SCAP_DIR/oval/"`
      ovaldir_xml_files=`find "$SCAP_DIR/oval/" -name "*.xml" -type f 2> /dev/null`

      for xml_file in $ovaldir_xml_files
      do
        if [ -z `echo "$oval_files_sorted" | grep -x "$xml_file"` ]
        then
          log_warning "Cannot find valid OVAL timestamp in '$xml_file'."
        fi
      done

      for ovalfile in $oval_files_sorted
      do
        if [ `stat -c "%Y" "$ovalfile" | cut -d " " -f 1 | tr -d "-"` -ge $DB_LASTUPDATE ] || [ 1 = $REBUILD_OVAL ]
        then
          oval_timestamp=`xsltproc "$SCAP_RES_DIR/oval_timestamp.xsl" "$ovalfile" | sed -e 's/T.*//' | tr -d "-"`

          if [ 1 = "$REBUILD_OVAL" ]
          then
            OVAL_REFDATE=0
          else
            OVAL_REFDATE=`$SQLITE3 $SCAP_DB "SELECT date(max(modification_time),'unixepoch') FROM ovaldefs WHERE xml_file = '${ovalfile##$SCAP_DIR/}';" | tr -d "-"`
            if [ -z "$OVAL_REFDATE" ]
            then
              OVAL_REFDATE=0
            fi
          fi

          if [ $oval_timestamp -ge $OVAL_REFDATE ]
          then
            log_info "Updating $ovalfile"
            xsltproc --stringparam filename "${ovalfile##$SCAP_DIR/}" --stringparam refdate "$OVAL_REFDATE" "$SCAP_RES_DIR/oval_update.xsl" "$ovalfile" | \
            $SQLITE3 $SCAP_DB
            updated_ovaldefs=1
          else
            log_info "Skipping $ovalfile, file has older timestamp than latest OVAL definition in database. (This is not an error)"
          fi
        else
          log_info "Skipping $ovalfile, file is older than last revision (this is not an error)."
        fi
      done
    else
      log_warning "No XML files found in $SCAP_DIR/oval/."
    fi
  fi

  update_scap_db_private
  update_cvss

  sqlite3 $SCAP_DB "update meta set value ='`date +%s`' where name = 'last_update';"
}

sync_scapdata(){
  if [ -e $ACCESSKEY ]
  then
    log_notice "Found Greenbone Security Feed subscription file, trying to synchronize with Greenbone SCAP data Repository ..."
    notsynced=1
    retried=0

    if [ $REFRESH_ONLY -eq 1 ]
    then
      notsynced=0
    fi

    mkdir -p "$BASE_SYNC_DIR"
    read feeduser < $ACCESSKEY
    custid=`head -1 $ACCESSKEY | cut -d @ -f 1`
    if [ -z $feeduser ] || [ -z $custid ]
    then
      log_err "Could not determine credentials, aborting synchronization."
      exit 1
    fi
    while [ 0 -ne "$notsynced" ]
    do
      # --protocol=29 is a workaround for a known bug in rsync 3.0.3
      if [ -e /admin/ezcli.state ]
      then
        gsmproxy=`grep proxy_feed /admin/ezcli.state | sed -e 's/^.*\/\///' -e 's/:/ /' -e 's/[\t ]*$//'`
        PORT=`grep ^syncport /admin/ezcli.state | sed -e "s/^syncport\t//g"`
      fi
      if [ -z "$gsmproxy" ] || [ "$gsmproxy" = "proxy_feed" ]
      then
        RSYNC_SSH_PROXY_CMD=""
      else
        if [ -r /admin/proxyauth ] && [ -s /admin/proxyauth ]; then
          RSYNC_SSH_PROXY_CMD="-o \"ProxyCommand corkscrew $gsmproxy %h %p /admin/proxyauth\""
        else
          RSYNC_SSH_PROXY_CMD="-o \"ProxyCommand corkscrew $gsmproxy %h %p\""
        fi
      fi
      rsync -e "ssh $RSYNC_SSH_OPTS $RSYNC_SSH_PROXY_CMD -p $PORT -i $ACCESSKEY" -ltvrP --protocol=29 --chmod=D+x $RSYNC_DELETE $RSYNC_COMPRESS $custid@feed.greenbone.net:/scap-data $BASE_SYNC_DIR
      if [ 0 != "$?" ] ; then
        log_err "rsync failed, aborting synchronization."
        exit 1
      fi
      notsynced=0
    done
    log_notice "Synchronization with the Greenbone SCAP data Repository successful."

    update_scap_db
  else
    log_err "gsf-access-key not found, aborting synchronization."
    exit 1
  fi
}

do_self_test () {
  if [ -z "$SELFTEST_STDERR" ]
  then
    SELFTEST_STDERR=0
  fi

  if [ -z "$SQLITE" ]; then
    if [ 0 -ne $SELFTEST_STDERR ]
    then
      echo "sqlite3 not found (required)." 1>&2
    fi
    log_err "sqlite3 not found (required)."
    SELFTEST_FAIL=1
  fi

  if [ -z "$RSYNC" ]
  then
    if [ 0 -ne $SELFTEST_STDERR ]
    then
      echo "rsync not found (required)." 1>&2
    fi
    log_err "rsync not found (required)."
    SELFTEST_FAIL=1
  fi

  if [ ! -d $BASE_SYNC_DIR ]
  then
    if [ 0 -ne $SELFTEST_STDERR ]
    then
      echo "BASE_SYNC_DIR ($BASE_SYNC_DIR) not found." 1>&2
    fi
    log_err "BASE_SYNC_DIR ($BASE_SYNC_DIR) not found."
    SELFTEST_FAIL=1
  fi
}

while test $# -gt 0; do
 case "$1" in
        --version)
          echo $VERSION
          exit 0
           ;;
        --identify)
          echo "SCAPSYNC|$SCRIPT_NAME|$VERSION|$FEED_NAME|$RESTRICTED|SCAPSYNC"
          exit 0
          ;;
        --describe)
          do_describe
          exit 0
          ;;
        --feedversion)
          do_feedversion
          exit 0
          ;;
        --help)
          do_help
          exit 0
          ;;
        --verbose)
          VERBOSE=1
          ;;
        --refresh)
          REFRESH_ONLY=1
          ;;
        --refresh-private)
          REFRESH_ONLY=1
          REFRESH_PRIVATE_ONLY=1
          ;;
        --selftest)
          SELFTEST_FAIL=0
          SELFTEST_STDERR=1
          do_self_test
          exit $SELFTEST_FAIL
          ;;
        --feedcurrent)
          is_feed_current
          exit $?
          ;;
      esac
      shift
done

SELFTEST_FAIL=0
do_self_test
if [ $SELFTEST_FAIL -ne 0 ]
then
  exit 1
fi

is_db_broken
if [ $DB_BROKEN -eq 1 ]
then
  log_notice "Purging corrupted database."
  rm -f $SCAP_DB
else
  is_feed_current
  if [ $FEED_CURRENT -eq 1 ] && [ $REFRESH_ONLY -ne 1 ]
  then
    log_notice "Feed is already current, skipping synchronization."
    exit 0
  fi
fi
sync_scapdata

exit 0
