#!/bin/sh
#
# greenbone-nvt-sync
# This script synchronizes an OpenVAS installation with the Greenbone Security
# Feed.
#
# Authors:
# Lukas Grunwald <lukas.grunwald@greenbone.net>
# Jan-Oliver Wagner <jan-oliver.wagner@greenbone.net>
# Michael Wiegand <michael.wiegand@intevation.de>
#
# Copyright (C) 2009-2014 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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.

# If you modify this script, please prefix the version
# with some characters in order to make it newer than
# any follow-up version to prevent that your version
# will be overwritten.
VERSION=20141208

# SETTINGS
# ========
# PRIVATE_SUBDIR defines a subdirectory of the NVT 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 \"$PRIVATE_SUBDIR/\"",
# which means that files which are not part of the feed or private directory
# will be deleted.
RSYNC_DELETE="--delete --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"

# RSYNC_CHMOD specifies the permissions to chmod the files to.
RSYNC_CHMOD="--perms --chmod=Fugo+r,Fug+w,Dugo-s,Dugo+rx,Dug+w"

# Verbosity flag for rsync. "-q" means a quiet rsync, "-v" a verbose rsync.
RSYNC_VERBOSE="-q"

# RSYNC_OPTIONS controls the general parameters for the rsync connection.
# Note: "--protocol=29" is a workaround for a known bug in rsync 3.0.3
RSYNC_OPTIONS="--links --times --omit-dir-times $RSYNC_VERBOSE --recursive --partial --progress --protocol=29"

# Script and feed information which will be made available to user through
# command line options and automated tools.
SCRIPT_NAME="greenbone-nvt-sync"
RESTRICTED=1

# Result of selftest () is stored here. If it is not 0, the selftest has failed
# and the sync script is unlikely to work.
SELFTEST_FAIL=0

# Port to use for synchronization. Default value is 24.
PORT=24

# Directory where pidfiles are located
PIDFILEDIR="/var/run"

# Directory where the OpenVAS configuration is located
OPENVAS_SYSCONF_DIR="/etc/openvas"

# Location of the GSF Access Key
ACCESS_KEY="$OPENVAS_SYSCONF_DIR/gsf-access-key"

# 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 OpenVAS
# Scanner and OpenVAS Manager. This can be controlled via
# the --refresh parameter.
REFRESH_ONLY=0

# If SYNC_ONLY is set to 1, the sync script will only perform the
# synchronization but will not update OpenVAS Scanner nor OpenVAS Manager.
# This can be controlled via the --sync-only parameter.
SYNC_ONLY=0

# 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"

# Source configuration file if it is readable
[ -r $OPENVAS_SYSCONF_DIR/greenbone-nvt-sync.conf ] && . $OPENVAS_SYSCONF_DIR/greenbone-nvt-sync.conf

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

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

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

stderr_write ()
{
  echo "$1" > /dev/stderr
}

init_sync ()
{
  do_self_test
  if [ $SELFTEST_FAIL -ne 0 ] ; then
    exit $SELFTEST_FAIL
  fi

  if [ -z "$NVT_DIR" ]
  then
    NVT_DIR="/var/lib/openvas/plugins"
  fi

  INFOFILE="$NVT_DIR/plugin_feed_info.inc"
  if [ -r $INFOFILE ] ; then
    FEED_VERSION=`grep PLUGIN_SET $INFOFILE | sed -e 's/[^0-9]//g'`
    FEED_NAME=`awk -F\" '/PLUGIN_FEED/ { print $2 }' $INFOFILE`
    FEED_VENDOR=`awk -F\" '/FEED_VENDOR/ { print $2 }' $INFOFILE`
    FEED_HOME=`awk -F\" '/FEED_HOME/ { print $2 }' $INFOFILE`
    FEED_PRESENT=1
  else
    FEED_PRESENT=0
  fi

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

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

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

  FEED_CURRENT=0
}

is_feed_current () {
  if [ -z "$FEED_VERSION" ]
  then
    log_write "Could not determine feed version."
    FEED_CURRENT=0
    return $FEED_CURRENT
  fi

  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

  read feeduser < $ACCESS_KEY
  custid=`awk -F@ 'NR > 1 { exit }; { print $1 }' $ACCESS_KEY`
  if [ -z "$feeduser" ] || [ -z "$custid" ]
  then
    log_err "Could not determine credentials, aborting synchronization."
    exit 1
  fi

  if [ "$gsmproxy" = "proxy_feed" ] || [ -z "$gsmproxy" ]
  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

  FEED_INFO_TEMP_DIR=`mktemp -d`

  rsync -e "ssh $RSYNC_SSH_OPTS $RSYNC_SSH_PROXY_CMD -p $PORT -i $ACCESS_KEY" $RSYNC_OPTIONS $RSYNC_DELETE $RSYNC_COMPRESS $RSYNC_CHMOD "$feeduser"plugin_feed_info.inc $FEED_INFO_TEMP_DIR

  if [ $? -ne 0 ]
  then
    log_err "Error: rsync failed."
    rm -rf "$FEED_INFO_TEMP_DIR"
    exit 1
  fi

  FEED_VERSION_SERVER=`grep PLUGIN_SET $FEED_INFO_TEMP_DIR/plugin_feed_info.inc | sed -e 's/[^0-9]//g'`

  if [ -z "$FEED_VERSION_SERVER" ]
  then
    echo "Could not determine server feed version."
    rm -rf $FEED_INFO_TEMP_DIR
    return -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
}

sync_nvts(){
  if [ $ENABLED -ne 1 ] && [ $REFRESH_ONLY -ne 1 ]
  then
    log_write "NVT synchronization is disabled, exiting."
    exit 0
  fi

  if [ $REFRESH_ONLY -eq 1 ]
  then
    return
  fi

  log_write "Synchronizing NVTs from the Greenbone Security Feed into $NVT_DIR..."
  if [ $FEED_PRESENT -eq 1 ] ; then
    FEEDCOUNT=`grep -E "nasl$|inc$" $NVT_DIR/md5sums | wc -l`
    log_write "Current status: Using $FEED_NAME at version $FEED_VERSION ($FEEDCOUNT NVTs)"
  else
    log_write "Current status: No feed installed."
  fi
  notsynced=1
  retried=0

  mkdir -p "$NVT_DIR"
  read feeduser < $ACCESS_KEY
  custid=`awk -F@ 'NR > 1 { exit }; { print $1 }' $ACCESS_KEY`
  if [ -z "$feeduser" ] || [ -z "$custid" ]
  then
    log_err "Could not determine credentials, aborting synchronization."
    exit 1
  fi
  while [ $notsynced -eq 1 ]
  do
    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 [ "$gsmproxy" = "proxy_feed" ] || [ -z "$gsmproxy" ]
    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 $ACCESS_KEY" $RSYNC_OPTIONS $RSYNC_DELETE $RSYNC_COMPRESS $RSYNC_CHMOD $feeduser $NVT_DIR
    if [ $? -ne 0 ]  ; then
      log_err "rsync failed, aborting synchronization."
      exit 1
    fi
    eval "cd \"$NVT_DIR\" ; md5sum -c --status \"$NVT_DIR/md5sums\""
    if [ $? -ne 0 ]  ; then
      if [ -n "$retried" ]
      then
        log_err "Feed integrity check failed twice, aborting synchronization."
        exit 1
      else
        log_write "The feed integrity check failed. This may be due to a concurrent feed update or other temporary issues."
        log_write "Sleeping 15 seconds before retrying ..."
        sleep 15
        retried=1
      fi
    else
      notsynced=0
    fi
  done
  log_write "Synchronization with the Greenbone Security Feed successful."
  init_sync
  if [ $FEED_PRESENT -eq 1 ] ; then
    FEEDCOUNT=`grep -E "nasl$|inc$" $NVT_DIR/md5sums | wc -l`
    log_write "Current status: Using $FEED_NAME at version $FEED_VERSION ($FEEDCOUNT NVTs)"
  else
    log_write "Current status: No feed installed."
  fi
}

update_openvassd (){
  if [ $SYNC_ONLY -eq 1 ]
  then
    return
  fi

  SCANNER_PIDFILE="$PIDFILEDIR/openvassd.pid"
  if [ -r "$SCANNER_PIDFILE" ]
  then
    SCANNER_PID=`cat $SCANNER_PIDFILE`
    kill -HUP $SCANNER_PID
    log_write "Sent signal to OpenVAS Scanner to update (PID $SCANNER_PID)."
  else
    log_err "$PIDFILEDIR/openvassd.pid not found, not updating OpenVAS Scanner."
    log_write "Please restart OpenVAS Scanner or send SIGHUP to it and let OpenVAS"
    log_write "Manager do a '--update' or a '--rebuild' to enable new NVTs for use."
    SYNC_ONLY=1
  fi
}

update_openvasmd (){
  if [ $SYNC_ONLY -eq 1 ]
  then
    return
  fi

  MANAGER_PIDFILE="$PIDFILEDIR/openvasmd.pid"
  if [ -r "$MANAGER_PIDFILE" ]
  then
    MANAGER_PID=`cat $MANAGER_PIDFILE`
    kill -HUP $MANAGER_PID
    log_write "Sent signal to OpenVAS Manager to update (PID $MANAGER_PID)."
  else
    log_err "$PIDFILEDIR/openvasmd.pid not found, not updating OpenVAS Manager."
    log_write "Please let OpenVAS Manager do a '--update' or a '--rebuild' to"
    log_write "enable new NVTs for use."
  fi
}

do_self_test ()
{
  RSYNC_AVAIL=`command -v rsync`
  if [ $? -ne 0 ] ; then
    SELFTEST_FAIL=1
    stderr_write "The rsync binary could not be found."
  fi
  MD5SUM_AVAIL=`command -v md5sum`
  if [ $? -ne 0 ] ; then
    SELFTEST_FAIL=1
    stderr_write "The md5sum binary could not be found."
  fi
  if [ ! -s $ACCESS_KEY ] ; then
    SELFTEST_FAIL=1
    stderr_write "No valid Greenbone Security Feed subscription file found."
    # else
    # Test rsync
    # Validate key
  fi
}

do_describe ()
{
  if [ -z "$NVT_DIR" ] ; then
    init_sync
  fi
  echo "This script synchronizes an NVT 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 [ -z "$NVT_DIR" ] ; then
    init_sync
  fi
  if [ $FEED_PRESENT -eq 1 ] ; then
    echo $FEED_VERSION
  else
    stderr_write "The file containing the feed version could not be found."
    exit 1
  fi
}

do_sync ()
{
  init_sync

  if [ $REFRESH_ONLY -ne 1 ]
  then
    is_feed_current
  fi

  if [ $FEED_CURRENT -eq 1 ] && [ $REFRESH_ONLY -ne 1 ]
  then
    log_write "Feed is already current, skipping synchronization."
  else
    sync_nvts
    update_openvassd
    sleep 1 # to ensure sd started working before md starts
    update_openvasmd
  fi
}

do_help () {
  echo "$0: Sync NVT data"
  echo " --describe	display current feed info"
  echo " --feedcurrent	just check if feed is up-to-date"
  echo " --feedversion	display version of this feed"
  echo " --help		display this help"
  echo " --identify	display information"
  echo " --nvtdir dir	set dir as NVT directory"
  echo " --refresh	update database without downloading new state"
  echo " --selftest	perform self-test"
  echo " --sync-only	download new state without updating database"
  echo " --verbose	makes the sync process print details"
  echo " --version	display version"
  echo ""
  exit 0
}

while test $# -gt 0; do
  case "$1" in
    --version)
      echo $VERSION
      exit 0
      ;;
    --identify)
      init_sync
      echo "NVTSYNC|$SCRIPT_NAME|$VERSION|$FEED_NAME|$RESTRICTED|NVTSYNC"
      exit 0
      ;;
    --selftest)
      do_self_test
      exit $SELFTEST_FAIL
      ;;
    --describe)
      do_describe
      exit 0
      ;;
    --feedversion)
      do_feedversion
      exit 0
      ;;
    --help)
      do_help
      exit 0
      ;;
    --sync-only)
      SYNC_ONLY=1
      ;;
    --refresh)
      REFRESH_ONLY=1
      ;;
    --nvt-dir)
      NVT_DIR="$2"
      shift
      ;;
    --feedcurrent)
      init_sync
      is_feed_current
      exit $?
      ;;
    --verbose)
      RSYNC_VERBOSE="-v"
      ;;
  esac
  shift
done

do_sync

exit 0

