#!/bin/sh
# Copyright (C) 2011-2018 Greenbone Networks GmbH
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# 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.

# This script synchronizes a GVM installation with the
# CERT data from either the Greenbone Security Feed (in
# case a GSF access key is present) or else from the Greenbone
# Community Feed.

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


########## SETTINGS
########## ========

# 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 cert.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 feed.xml --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

# SCRIPT_NAME is the name the scripts will use to identify itself and to mark
# log messages.
SCRIPT_NAME="greenbone-certdata-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"

# LOCK_FILE is the name of the file used to lock the feed during sync or update.
LOCK_FILE=/tmp/gvm-sync-cert


########## GLOBAL VARIABLES
########## ================

VERSION=8.0.1

[ -r "/etc/gvm/greenbone-certdata-sync.conf" ] && . "/etc/gvm/greenbone-certdata-sync.conf"

CERT_DIR="/var/lib/gvm/cert-data"

if [ -z "$DROP_USER" ]; then
  DROP_USER=""
fi

TIMESTAMP="$CERT_DIR/timestamp"

ACCESSKEY="/etc/gvm/gsf-access-key"

# Note when running as root or restart as $DROP_USER if defined
if [ $(id -u) -eq 0 ]
then
  if [ -z "$DROP_USER" ]
  then
    log_notice "Running as root"
  else
    log_notice "Started as root, restarting as $DROP_USER"
    su --shell /bin/sh --command "$0 $*" "$DROP_USER"
    exit $?
  fi
fi

# Determine whether a GSF access key is present. If yes,
# then use the Greenbone Security Feed. Else use the
# Greenbone Community Feed.
if [ -e $ACCESSKEY ]
then
  RESTRICTED=1

  if [ -z "$FEED_NAME" ] ; then
    FEED_NAME="Greenbone CERT 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

else
  RESTRICTED=0

  if [ -z "$FEED_NAME" ] ; then
    FEED_NAME="OpenVAS CERT Feed"
  fi

  if [ -z "$FEED_VENDOR" ] ; then
    FEED_VENDOR="The OpenVAS Project"
  fi

  if [ -z "$FEED_HOME" ] ; then
    FEED_HOME="http://www.openvas.org/"
  fi

  if [ -z "$COMMUNITY_CERT_RSYNC_FEED" ]; then
    COMMUNITY_CERT_RSYNC_FEED=rsync://feed.openvas.org:/cert-data
    # An alternative syntax which might work if the above doesn't:
    # COMMUNITY_CERT_RSYNC_FEED=rsync@feed.openvas.org::cert-data
  fi

fi

RSYNC=`command -v rsync`


########## FUNCTIONS
########## =========

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

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

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

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

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

  mkdir -p $CERT_DIR
  echo '<feed id="6315d194-4b6a-11e7-a570-28d24461215b">' > $CERT_DIR/feed.xml
  echo "<type>CERT</type>" >> $CERT_DIR/feed.xml
  echo "<name>$FEED_NAME</name>" >> $CERT_DIR/feed.xml
  echo "<version>$FEED_VERSION</version>" >> $CERT_DIR/feed.xml
  echo "<vendor>$FEED_VENDOR</vendor>" >> $CERT_DIR/feed.xml
  echo "<home>$FEED_HOME</home>" >> $CERT_DIR/feed.xml
  echo "<description>" >> $CERT_DIR/feed.xml
  echo "This script synchronizes a CERT collection with the '$FEED_NAME'." >> $CERT_DIR/feed.xml
  echo "The '$FEED_NAME' is provided by '$FEED_VENDOR'." >> $CERT_DIR/feed.xml
  echo "Online information about this feed: '$FEED_HOME'." >> $CERT_DIR/feed.xml
  echo "</description>" >> $CERT_DIR/feed.xml
  echo "</feed>" >> $CERT_DIR/feed.xml
}

create_tmp_key () {
  KEYTEMPDIR=`mktemp -d`
  cp "$ACCESSKEY" "$KEYTEMPDIR"
  TMPACCESSKEY="$KEYTEMPDIR/gsf-access-key"
  chmod 400 "$TMPACCESSKEY"
}

remove_tmp_key () {
  rm -rf "$KEYTEMPDIR"
}

do_describe () {
  echo "This script synchronizes a CERT 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
      cat $TIMESTAMP
  fi
}

# This function uses gos-state-manager to get information about the settings.
# gos-state-manager is only available on a Greenbone OS.
# If gos-state-manager is missing the settings values can not be retrieved.
#
# Input: option
# Output: value as string or empty String if gos-state-manager is missing
#         or option not set
get_value ()
{
  value=""
  key=$1
  if which gos-state-manager >/dev/null 2>&1
  then
    if gos-state-manager get "$key.value" >/dev/null 2>&1
    then
      value="$(gos-state-manager get "$key.value")"
    fi
  fi
  echo "$value"
}

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_at_host=`head -1 $ACCESSKEY | cut -d : -f 1`

    if [ -z "$feeduser" ] || [ -z "$custid_at_host" ]
    then
      log_err "CERT synchronization: Could not determine credentials, aborting synchronization."
      rm -rf "$FEED_INFO_TEMP_DIR"
      exit 1
    fi
    echo "custid_at_host = '$custid_at_host'"

    gsmproxy=$(get_value proxy_feed | sed -r -e 's/^.*\/\///' -e 's/:([0-9]+)$/ \1/')
    syncport=$(get_value syncport)
    if [ "$syncport" ]
    then
      PORT="$syncport"
    fi

    if [ -z "$gsmproxy" ] || [ "$gsmproxy" = "proxy_feed" ]
    then
      RSYNC_SSH_PROXY_CMD=""
    else
      if [ -e $GVM_SYSCONF_DIR/proxyauth ] && [ -r $GVM_SYSCONF_DIR/proxyauth ]; then
        RSYNC_SSH_PROXY_CMD="-o \"ProxyCommand corkscrew $gsmproxy %h %p $GVM_SYSCONF_DIR/proxyauth\""
      else
        RSYNC_SSH_PROXY_CMD="-o \"ProxyCommand corkscrew $gsmproxy %h %p\""
      fi
    fi
    create_tmp_key
    rsync -e "ssh $RSYNC_SSH_OPTS $RSYNC_SSH_PROXY_CMD -p $PORT -i $TMPACCESSKEY" -ltvrP --chmod=D+x $RSYNC_DELETE $RSYNC_COMPRESS $custid_at_host:/cert-data/timestamp "$FEED_INFO_TEMP_DIR"
    if [ $? -ne 0 ]
    then
      log_err "rsync failed, aborting synchronization."
      rm -rf "$FEED_INFO_TEMP_DIR"
      remove_tmp_key
      exit 1
    fi
    remove_tmp_key
  else
    log_notice "No Greenbone Security Feed access key found, falling back to Greenbone Community Feed"
    eval "$RSYNC -ltvrP \"$COMMUNITY_CERT_RSYNC_FEED/timestamp\" \"$FEED_INFO_TEMP_DIR\""
    if [ $? -ne 0 ]
    then
      log_err "rsync failed, aborting synchronization."
      rm -rf "$FEED_INFO_TEMP_DIR"
      exit 1
    fi
  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
}

do_help () {
  echo "$0: Sync CERT data"

  if [ -e $ACCESSKEY ]
  then
    echo "GSF access key found: Using Greenbone Security Feed"
  else
    echo "No GSF access key found: Using Community Feed"
  fi

  echo " --describe     display current feed info"
  echo " --feedversion  display version of this feed"
  echo " --help         display this help"
  echo " --identify     display information"
  echo " --selftest     perform self-test"
  echo " --version      display version"
  echo ""
  exit 0
}

do_rsync_community_feed () {
  if [ -z "$RSYNC" ]; then
    log_err "rsync not found!"
  else
    log_notice "Using rsync: $RSYNC"
    log_notice "Configured CERT data rsync feed: $COMMUNITY_CERT_RSYNC_FEED"
    mkdir -p "$CERT_DIR"
    eval "$RSYNC -ltvrP $RSYNC_DELETE \"$COMMUNITY_CERT_RSYNC_FEED\" \"$CERT_DIR\""
    if [ $? -ne 0 ] ; then
      log_err "rsync failed. Your CERT data might be broken now."
      exit 1
    fi
  fi
}

do_sync_community_feed () {
  if [ -z "$RSYNC" ] ; then
    log_err "rsync not found!"
    log_err "No utility available in PATH environment variable to download Feed data"
    exit 1
  else
    log_notice "Will use rsync"
    do_rsync_community_feed
  fi
}

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

    mkdir -p "$CERT_DIR"
    read feeduser < $ACCESSKEY
    custid_at_host=`head -1 $ACCESSKEY | cut -d : -f 1`
    if [ -z "$feeduser" ] || [ -z "$custid_at_host" ]
    then
      log_err "Could not determine credentials, aborting synchronization."
      exit 1
    fi

    while [ 1 -eq $notsynced ]
    do

      gsmproxy=$(get_value proxy_feed | sed -r -e 's/^.*\/\///' -e 's/:([0-9]+)$/ \1/')
      syncport=$(get_value syncport)
      if [ "$syncport" ]
      then
        PORT="$syncport"
      fi

      if [ -z "$gsmproxy" ] || [ "$gsmproxy" = "proxy_feed" ]
      then
        RSYNC_SSH_PROXY_CMD=""
      else
        if [ -e $GVM_SYSCONF_DIR/proxyauth ] && [ -r $GVM_SYSCONF_DIR/proxyauth ]; then
          RSYNC_SSH_PROXY_CMD="-o \"ProxyCommand corkscrew $gsmproxy %h %p $GVM_SYSCONF_DIR/proxyauth\""
        else
          RSYNC_SSH_PROXY_CMD="-o \"ProxyCommand corkscrew $gsmproxy %h %p\""
        fi
      fi
      create_tmp_key
      rsync -e "ssh $RSYNC_SSH_OPTS $RSYNC_SSH_PROXY_CMD -p $PORT -i $ACCESSKEY" -ltvrP --chmod=D+x $RSYNC_DELETE $RSYNC_COMPRESS $custid_at_host:/cert-data/ $CERT_DIR
      if [ 0 -ne "$?" ] ; then
        log_err "rsync failed, aborting synchronization."
        remove_tmp_key
        exit 1
      fi
      remove_tmp_key
      notsynced=0
    done
    log_notice "Synchronization with the Greenbone CERT data Repository successful."
    echo
  else
    log_notice "No Greenbone Security Feed accees key found, falling back to Greenbone Community Feed"
    do_sync_community_feed
  fi

  write_feed_xml
}

do_self_test () {
  if [ -z "$SELFTEST_STDERR" ]
  then
    SELFTEST_STDERR=0
  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
}


########## START
########## =====

write_feed_xml

while test $# -gt 0; do
 case "$1" in
        --version)
          echo $VERSION
          exit 0
           ;;
        --identify)
          echo "CERTSYNC|$SCRIPT_NAME|$VERSION|$FEED_NAME|$RESTRICTED|CERTSYNC"
          exit 0
          ;;
        --describe)
          do_describe
          exit 0
          ;;
        --feedversion)
          do_feedversion
          exit 0
          ;;
        --help)
          do_help
          exit 0
          ;;
        --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_feed_current
if [ $FEED_CURRENT -eq 1 ]
then
  log_notice "Feed is already current, skipping synchronization."
  exit 0
fi
(
  flock -n 9
  date > $LOCK_FILE
  if [ $? -eq 1 ] ; then
    log_notice "Sync in progress, exiting."
    exit 1
  fi
  sync_certdata
  echo -n > $LOCK_FILE
) 9>$LOCK_FILE

exit 0
