#!/bin/sh
# Copyright 2009-2013 Canonical, Ltd.
# Authors:
#  Jamie Strandboge <jamie@canonical.com>
#  Kees Cook <kees@ubuntu.com>
#  Marc Deslauriers <marc.deslauriers@canonical.com>
#
# Script to help triage CVEs based on Triage Frequency in $UCT/README
#

set -e

loc=$(readlink -f "$(dirname "$0")/..")

uctconf="$HOME/.ubuntu-cve-tracker.conf"
if [ -s "$uctconf" ]; then
    . "$uctconf"
else
    echo "Could not find '$uctconf'" >&2
    exit 1
fi

is_http_url() {
    url="$1"
    echo "$url" | grep -q "^http"
}

download() {
    url="$1"
    # if http, then download it with wget, otherwise, try to rsync it
    if is_http_url "$url" ; then
        # Force the gzipped JSON to be downloaded
        # Required for nvdcve-1.1-recent.json as of 2015-10-16
        url="${url}.gz"
        file=$(basename "$url")

        echo "wget -N \"$url\""
        wget -N "$url" ||:
        gunzip -f "$file"
    else
        echo "rsync -vz --progress \"$url\" ."
        rsync -vz --progress "$url" .
    fi
    echo "---"
}

download_yearly_files() {
    base_url="${nvd_loc:-https://nvd.nist.gov/feeds/json/cve/1.1}/"
    # if http, download each file sequentially, otherwise, use glob with rsync
    if is_http_url "$base_url" ; then
        current_year=$(date +%Y)
        for year in $(seq 2004 "$current_year"); do
            download "${base_url}nvdcve-1.1-${year}.json"
        done
    else
        # TODO: Make Y3K compliant
        download "${base_url}nvdcve-1.1-2*.json"
    fi
}

update_uct() {
    # update UCT git tree
    if [ -n "$loc" ] && [ -d "${loc}/.git" ] ; then
        echo "Updating UCT"
        git fetch -p -t && git pull --ff-only || echo "Error updating UCT tree"
    fi
}

update_subprojects() {
    # update subprojects git tree
    if [ -n "$loc" ] && [ -d "${loc}/subprojects/" ]; then
        echo "Updating subprojects"
        cd "$loc/subprojects/"
        git fetch -p -t && git pull --ff-only || echo "Error updating subprojects tree"
        cd "$loc"
    fi
}

update_embargoed() {
    # update embargoed git tree
    if [ -n "$loc" ] && [ -d "${loc}/embargoed/" ]; then
        echo "Updating embargoed"
        cd "$loc/embargoed/"
        git fetch -p -t && git pull --ff-only || echo "Error updating embargoed tree"
        cd "$loc"
    fi
}

update_debian() {
    # Update Debian SVN
    if [ -n "$secure_testing_path" ] && [ -d "${secure_testing_path}/.git" ] ; then
        echo "Updating '$secure_testing_path'"
        cd "$secure_testing_path"
        git fetch -p -t && git pull --ff-only || echo "Error updating Debian tree"
        cd "$loc"
    fi
}

update_files() {
    download "${mitre_loc:-https://cve.mitre.org/data/downloads}/allitems.xml"
    download_yearly_files
    download "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-recent.json"
}

full_refresh() {
    # get PubDate
    echo "update PubDate (check-cves --refresh nvdcve-*.json) ..."
    ./scripts/check-cves --refresh nvdcve-*.json

    # get authoritative descriptions
    echo "get authoritative descriptions (check-cves --refresh ./allitems.xml) ..."
    ./scripts/check-cves --refresh ./allitems.xml
}

kernel_team_merge() {
    echo "Merging with kernel team CVE tracker"
    local kernel_remote='kernel-team'
    local kernel_url="https://git.launchpad.net/~canonical-kernel-team/ubuntu-cve-tracker"

    if ! (git remote | grep -q "^${kernel_remote}$") ; then
        git remote add "${kernel_remote}" "${kernel_url}"
    fi

    git fetch -q "${kernel_remote}"
    if ! [ "$(git rev-list HEAD..${kernel_remote}/master --count)" -eq 0 ] ; then
        echo "Missing merge commits from the Kernel Team's CVE Tree"
	git log -p HEAD..${kernel_remote}/master
    fi

    git merge --no-commit ${kernel_remote}/master
    echo "Merged with kernel team branch, please review and commit."
}

process_missing_debian() {
    echo "check-cves --import-missing-debian"
    ./scripts/check-cves --import-missing-debian
}

mistriaged() {
    echo "check-cves --mistriage 10"
    ./scripts/check-cves --mistriage 10
}

download_missing_rhsa() {
    tmpdir="$1"
    archive="$2.txt"
    fn="$tmpdir/$archive"
    url="https://www.redhat.com/archives/rhsa-announce/$archive.gz"

    echo "Downloading $url..."
    # curl and wget fail with redhat's web proxy
    w3m -dump_source "$url" > "$fn.gz"
    echo "Unzipping $fn.gz..."
    gzip -d "$fn.gz" || {
        # This can happen at the start of a new month and RedHat has provided
        # any advisories yet
        echo "(skipping '$fn.gz')"
        rm -f "$fn.gz"
        return
    }

    echo "locate_cves.py $fn..."
    ./scripts/locate_cves.py "$fn" >> "$tmpdir/redhat.mbox"
}

download_rhel8oval() {
    tmpdir="$1"
    archive="$2"
    fn="$tmpdir/$archive"
    url="https://www.redhat.com/security/data/oval/v2/RHEL8/$archive.bz2"

    echo "Downloading $url..."
    # curl and wget fail with redhat's web proxy
    w3m -dump_source "$url" > "$fn.bz2"
    echo "Unzipping $fn.bz2..."
    bzip2 -d "$fn.bz2"
}

process_missing_redhat() {
    tmpdir=$(mktemp -d)
    download_missing_rhsa "$tmpdir" "$(date +%Y-%B)"
    download_missing_rhsa "$tmpdir" "$(date --date="$(date +%Y-%m-15) -1 month" +%Y-%B)"
    ./scripts/check-cves --untriaged "$tmpdir/redhat.mbox"
    rm -rf "$tmpdir"

    tmpdir=$(mktemp -d)
    download_rhel8oval "$tmpdir" "rhel-8-including-unpatched.oval.xml"
    ./scripts/check-cves --rhel8oval "$tmpdir/rhel-8-including-unpatched.oval.xml"
    rm -rf "$tmpdir"
}

check_syntax() {
    echo "Running check-syntax"
    if ! ./scripts/check-syntax; then
        cat <<EOM

*** IMPORTANT ***
There were check-syntax failures that need to be addressed. Please make the
appropriate modifications before checking in your work.
EOM
    else
        echo "No syntax issues found"
    fi
}

check_needs_retire() {
    echo "Running cve_need_retire"
    output=$(./scripts/cve_need_retire -r)
    if [ -n "${output}" ] ; then
        cat <<EOM

The following CVEs should be retired:
EOM
    	echo "${output}"
    else
        echo "No CVEs needing retirement found"
    fi
}

nag_about_mailing_lists() {
    cat <<EOM

*** IMPORTANT ***
Mitre and NVD are no longer updated in a timely fashion. Please review
oss-security, linux-distros, nd vendor mailing lists for any new CVEs after
doing triage. This script pulls in RHSAs on Wednesdays and DSAs daily.

Eg, to triage oss-security (assumes in the UCT directory):
 <save last month to mbox file, ~/oss-sec.mbox>
 $ ./scripts/check-cves --mbox ~/oss-sec.mbox
EOM
}

prev_dir=$(pwd)
trap "cd '$prev_dir'" EXIT HUP INT QUIT TERM

# Determine mode
action=$(LC_TIME=C date +%a)
if [ ! -z "$1" ]; then
    action="$1"
fi

print_usage() {
  cat <<EOF
Usage: ./scripts/process_cves [ACTION]

If [ACTION] is omitted, the current day-of-the-week will be used.

[ACTION] can consist of one of the following:

help|usage - Print this message.
Mon        - Update UCT/embargoed/debian git repos, fetch and import CVEs from
             all NVD CVEs, and process up to 10 mistriaged CVEs.
Wed        - Update UCT/embargoed/debian git repos, fetch and import CVEs from
             recent NVD CVEs, import from Debian and Red Hat any missing CVEs.
redhat     - Import any missing CVEs from Red Hat
debian     - Import any missing CVEs from Debian
mistriaged - Process up to 10 CVEs that we previously marked as NFU but
             which Debian triaged against a particular package
update     - Update UCT/embargoed/debian git repos and NVD JSON but do no
             triage.
refresh    - Update UCT/embargoed/debian git repos and NVD JSON / MITRE XML
             and refresh local CVE descriptions and publication dates with
             authoritative info from MITRE/NVD.
merge      - Update UCT/embargoed/debian git repos and NVD JSON / MITRE XML
             and merge from the kernel-team's UCT fork.
CVE-XX-YYY - Update UCT/embargoed/debian git repos and NVD JSON / MITRE XML
             and process just the (comma-separated list of) CVEs
foo.json   - Update UCT/embargoed/debian git repos and process just the
             CVEs from the specified JSON file.
*          - Update UCT/embargoed/debian git repos, fetch and import CVEs from
             recent NVD CVEs, import any missing Debian CVEs.
EOF
}

case "$action" in
    Mon)
        update_uct
        update_embargoed
        update_subprojects
        update_debian
        update_files

        # kernel_team_merge
        echo "Skipping merge from kernel team"
        echo "check-cves nvdcve-*.json"
        ./scripts/check-cves nvdcve-*.json
        # this will also do missing debian
        mistriaged

        check_syntax
        nag_about_mailing_lists
        ;;
    Wed)
        update_uct
        update_embargoed
        update_subprojects
        update_debian
        update_files
        # kernel_team_merge
        echo "Skipping merge from kernel team"

        # process new ones
        echo "check-cves ./nvdcve-1.1-recent.json"
        ./scripts/check-cves ./nvdcve-1.1-recent.json
        echo "check-cves ./allitems.xml"
        ./scripts/check-cves ./allitems.xml

        process_missing_debian
        process_missing_redhat

        full_refresh
        check_needs_retire
        check_syntax
        nag_about_mailing_lists
        ;;
    redhat)
        process_missing_redhat
        ;;
    debian)
        update_debian
        process_missing_debian
        ;;
    mistriage*)
        update_debian
        mistriaged
        ;;
    update)
        # Do no triage
        update_uct
        update_embargoed
        update_subprojects
        update_debian
        update_files
        ;;
    refresh)
        # Do no triage
        update_uct
        update_embargoed
        update_subprojects
        update_debian
        update_files
        full_refresh
        ;;
    merge)
        # Do no triage
        update_uct
        update_embargoed
        update_subprojects
        update_debian
        update_files
        kernel_team_merge
        ;;
    CVE-*)
        # run triage on just a specific CVE
        update_uct
        update_embargoed
        update_subprojects
        update_debian
        echo "check-cves --cve ${action}"
        # add all NVD details as well so we can get date etc for it
        ./scripts/check-cves --cve ${action} nvdcve-*.json
        check_syntax
        ;;
    *.json)
        # run triage on a specific json file
        update_uct
        update_embargoed
        update_subprojects
        update_debian
        echo "check-cves ${action}"
        ./scripts/check-cves ${action}
        check_syntax
        ;;
    --help|help|usage)
        print_usage
        ;;
    *)
        update_uct
        update_embargoed
        update_subprojects
        update_debian
        update_files
        # kernel_team_merge
        echo "Skipping merge from kernel team"
        echo "check-cves ./nvdcve-1.1-recent.json"
        ./scripts/check-cves ./nvdcve-1.1-recent.json
        process_missing_debian
        check_syntax
        nag_about_mailing_lists
        ;;
esac

exit 0
