#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2024 - Greg Kroah-Hartman <gregkh@linuxfoundation.org>
#
# update_dyad - Update all .dyad files in the tree.
#
# 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:
#	update_dyad
#
# Requires:
#  dyad

# set to 1 to get some debugging logging messages (or use -v/--verbose option)
DEBUG=0

# 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

# Initialize our color variables if we are a normal terminal
if [[ -t 1 ]]; then
	txtred=$(tput setaf 1)          # Red
	txtgrn=$(tput setaf 2)          # Green
	txtblu=$(tput setaf 4)          # Blue
	txtcyn=$(tput setaf 6)          # Cyan
	txtrst=$(tput sgr0)             # Text reset
else
	txtred=""
	txtgrn=""
	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
}

#############################
# dbg()
#	if DEBUG is enabled, print out a message.
#	Can also be set with -v or --verbose command line option
#	Does so with an initial "#" so it can be easily filtered out
# arguments:
# 	"message to print"
# assumes:
#	DEBUG is defined to something
#############################
dbg()
{
	if [[ ${DEBUG} -ge 1 ]] ; then
		echo "${txtcyn}# ${1}${txtrst}"
	fi
}



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

# location of dyad helper script
dyad="${DIR}/dyad"

update_cve()
{
	local id=$1
	local string=$2
	local message=""
	local tmp_dyad
	local sha
	local cve
	local root
	local vuln_file
	local vulnerable_sha
	local vulnerable_option
	local diff_file
	local result
	local updated_file
	local diff

	tmp_dyad=$(mktemp "${TMPDIR}/${SCRIPT}XXXX.dyad" || exit 1)
	sha=$(cat "${id}")
	cve=$(echo "${id}" | cut -f 1 -d '.' | cut -f 4 -d '/')
	root=$(echo "${id}" | cut -f 1 -d '.')
	dbg "	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"
	dbg "	vuln_file=${vuln_file}"
	vulnerable_option=""
	vulnerable_sha=""
	if [[ -f "${vuln_file}" ]]; then
		vulnerable_sha=$(cat "${vuln_file}")
		vulnerable_option="--vulnerable=${vulnerable_sha}"
	fi

	# Create the new dyad file
	#echo "\"${DIR}\"/dyad ${vulnerable_option} ${sha} > ${root}.dyad"
	#"${DIR}"/dyad ${vulnerable_option} ${sha} > ${root}.dyad
	"${dyad}" ${vulnerable_option} ${sha} > ${tmp_dyad}
	result=$?
	if [[ "${result}" != 0 ]]; then
		# dyad failed, so report it and continue on
		echo "${txtred}Error:${txtrst} dyad failed to create ${txtcyn}${root}.dyad${txtrst} for commit ${txtgrn}${sha}${txtrst}"
		return
	fi

	# see if the dyad file EXCEPT for the dyad version if this isn't the first time the file is created
	if [[ ! -f "${root}.dyad" ]]; then
		# copy it into place
		mv -f "${tmp_dyad}" "${root}.dyad"
		updated_file+="${root}.dyad"
	else
		updated_file=""
		diff=$(diff -u "${root}.dyad" "${tmp_dyad}" | grep -v "dyad" | grep -v "^@@ " | grep "^[+|-]")
		#echo "diff json=${diff}"
		if [[ "${diff}" != "" ]] ; then
			mv -f "${tmp_dyad}" "${root}.dyad"
			updated_file+="${root}.dyad"
		else
			rm "${tmp_dyad}"
			dbg "	diff for dyad was empty"
		fi
	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 total 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

