#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2024 - Greg Kroah-Hartman <gregkh@linuxfoundation.org>
#
# cve_update - Update all existing CVE entries based on the latest information
#	       pulled from the git tree(s).
#
# Will look through the list of all published cve ids and run 'bippy' on them
# to update the mbox and json files.  It is recommended that after this
# happens, submit the json files to CVE again, if version numbers have changed.
#
# This is good to do after older stable kernels have been released as often
# CVEs are included in older stable kernels AFTER they show up in newer ones,
# and this keeps the database at CVE more up to date and friendly for others to
# rely on.  The mbox files generally shouldn't be resent, as that's just noise
# that no one wants to see.
#
# Usage:
#	cve_update
#
# Requires:
#  bippy

# Colors are good!
if [[ -t 1 ]]; then
	txtred=$(tput setaf 1)		# Red
	txtgrn=$(tput setaf 2)		# Green
	txtylw=$(tput setaf 3)		# Yellow
	txtblu=$(tput setaf 4)		# Blue
	txtcyn=$(tput setaf 6)		# Cyan
	txtrst=$(tput sgr0)		# Text reset
else
	txtred=""
	txtgrn=""
	txtylw=""
	txtblu=""
	txtcyn=""
	txtrst=""
fi

# set where the tool was run from,
# the name of our script,
# and the git version of it
DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
SCRIPT=${0##*/}

help() {
	echo "${SCRIPT}"
	echo "	Note, CVE_USER must be set to your CVE_USER email address"
	exit 1
}

if [[ "${TMPDIR}" == "" ]]; then
	echo "TMPDIR environment variable must be set, exiting ${SCRIPT}"
	exit 1
fi

CVE=$1

# don't use unset variables
set -o nounset

cd "${DIR}"/../ || exit 1

update_cve()
{
	local id=$1
	local string=$2
	local message=""
	local tmp_json
	local tmp_mbox
	local sha
	local cve
	local root
	local vuln_file
	local vulnerable_sha
	local vulnerable_option
	local diff_file
	local reference_file
	local reference_option
	local result
	local updated_file
	local diff
	local diff_option

	tmp_json=$(mktemp "${TMPDIR}/${SCRIPT}XXXX.json" || exit 1)
	tmp_mbox=$(mktemp "${TMPDIR}/${SCRIPT}XXXX.mbox" || exit 1)
	sha=$(cat "${id}")
	cve=$(echo "${id}" | cut -f 1 -d '.' | cut -f 4 -d '/')
	root=$(echo "${id}" | cut -f 1 -d '.')
	#echo "id=${id} sha=${sha} cve=${cve}"
	message+="Updating ${txtcyn}${cve}${txtrst} ${txtylw}${string}${txtrst}..."

	# Look to see if we have a "og_vuln" that is provided to us in a
	# published CVE.  This is used for when we can't determine it on our
	# own, but we have manually looked it up later on and added it to a
	# CVE-*.vulnerable file
	vuln_file="${root}.vulnerable"
	#echo "vuln_file=${vuln_file}"
	vulnerable_option=""
	vulnerable_sha=""
	if [[ -f "${vuln_file}" ]]; then
		vulnerable_sha=$(cat "${vuln_file}")
		vulnerable_option="--vulnerable=${vulnerable_sha}"
	fi

	diff_option=""
	diff_file="${root}.diff"
	if [[ -f "${diff_file}" ]]; then
		diff_option="--diff=${diff_file}"
	fi

	reference_option=""
	reference_file="${root}.reference"
	if [[ -f "${reference_file}" ]]; then
		reference_option="--reference=${reference_file}"
	fi

	# Create the new json and mbox files
	"${DIR}"/bippy --cve="${cve}" --sha="${sha}" --json="${tmp_json}" --mbox="${tmp_mbox}" --vulnerable="${vulnerable_sha}" "${diff_option}" "${reference_option}"
	result=$?
	if [[ "${result}" != 0 ]]; then
		# bippy failed, so report it and continue on
		echo "${txtred}Error:${txtrst} bippy failed to create ${txtcyn}${cve}${txtrst} for commit ${txtgrn}${sha}${txtrst}"
		return
	fi

	# see if the json and/or mbox files actually changed, EXCEPT for the bippy-VERSIONINFO string
	updated_file=""
	diff=$(diff -u "${root}.json" "${tmp_json}" | grep -v "${tmp_json}" | grep -v "${root}.json" | grep -v "bippy" | grep -v "^@@ " | grep "^[+|-]" | grep -v "@kernel.org" | grep -v "@linuxfoundation.org")
	#echo "diff json=${diff}"
	if [[ "${diff}" != "" ]] ; then
		mv -f "${tmp_json}" "${root}.json"
		updated_file+="${root}.json"
	else
		rm "${tmp_json}"
		#echo "diff for json was empty"
	fi

	diff=$(diff -u "${root}.mbox" "${tmp_mbox}" | grep -v "${tmp_mbox}" | grep -v "${root}.mbox" | grep -v "bippy-" | grep -v "^@@ " | grep "^[+|-]" | grep -v "@kernel.org" | grep -v "@linuxfoundation.org")
	#echo "diff mbox=${diff}"
	if [[ "${diff}" != "" ]] ; then
		mv -f "${tmp_mbox}" "${root}.mbox"
		updated_file+=" ${root}.mbox"
	else
		rm "${tmp_mbox}"
		#echo "diff for mbox was empty"
	fi
	if [[ "${updated_file}" == "" ]] ; then
		message+="	${txtgrn}Nothing changed${txtrst}"
	else
		message+="	Updated ${txtblu}${updated_file}${txtrst}"
	fi
	echo "${message}"
}

update_year() {
	local year=$1
	local threads=$(nproc)

	# get a count of ids for this year
	total_count=$(ls cve/published/${year}/*.sha1 | wc -l)
	count=0

	echo "Updating ${txtcyn}${total_count}${txtrst} CVE ids for ${txtgrn}${year}${txtrst} with ${txtcyn}${threads}${txtrst} processes..."
	for id in cve/published/${year}/*.sha1 ; do
		count=$((count + 1))
		count_string=$(printf "[%04d/%04d]" ${count} ${total_count})
		while :
		do
			if [[ $(jobs -p | wc -l) -lt ${threads} ]]; then
				#echo "id=${id}"
				update_cve "${id}" "${count_string}" &
				break
			else
				sleep 1
			fi
		done
	done
	wait
}

if [[ "${CVE}" == "" ]]; then
	# Nothing specified on the command line, so just update everything by
	# looping through all years
	for y in cve/published/* ; do
		year=$(echo "${y}" | cut -f 3 -d '/')
		update_year ${year}
	done
else
	# Either the year, or a specific CVE id is specified here.
	#
	# Test for a specific cve id first
	found=$(${DIR}/cve_search ${CVE})
	found_result=$?
	if [[ "${found_result}" == "0" ]]; then
		CVE_ROOT="${DIR}/../cve/"
		found=$(find "${CVE_ROOT}" -type f | grep -v testing | grep "${CVE}" | grep "sha1")
		#echo "found='${found}"
		if [[ "${found}" != "" ]]; then
			# strip off the CVE root, as that's what update_cve is expecting:
			update_cve "cve/${found/#$CVE_ROOT}"
			exit 0
		fi
	fi

	# Not a specific id, let's try to do this for a year
	#
	if [[ -d cve/published/${CVE} ]]; then
		update_year ${CVE}
		exit 0
	fi

	echo "${txtred}ERROR:${txtrst} ${txtcyn}${CVE}${txtrst} is not found or is not a year."
	exit 1
fi

