#!/usr/bin/perl -w
# dumps all CVSS scores for all released updates into CSV.
use strict;
use Data::Dumper;

my $dn = `dirname $0`;chomp($dn);
my $pwd = `pwd`;chomp($pwd);
if ($dn !~ /^\//) { $dn = $pwd . "/" . $dn; }
push @INC,$dn;

require CanDBReader;
require SMASHData;

&SMASHData::read_all_cached_issues();

my $mode = pop @ARGV;
$mode = "kernel" if (!defined($mode));

my @analysis = ();
my @nolivepatch = ();
my @nocvssv3 = ();
my @nocvssv3other = ();
my @differentcvssv3 = ();

my %unclosedbugs = ();

my %cve2cvss = ();

my %allcvss7 = ();

unlink("badscore.csv");

#open(CVSS,">allreports.txt");

foreach my $cve (sort keys %SMASHData::pkgstate) {
	my %prodstates = %{$SMASHData::pkgstate{$cve}};

	my $foundone = 0;
	foreach my $prod (keys %prodstates) {
		my %pkgstates = %{$prodstates{$prod}};
		if (grep(/kernel-source/,keys %pkgstates)) {
			if (!defined($CanDBReader::bugzillas{$cve})) {
				print "kernel CVE $cve has no bugzilla entry?\n";
				$foundone = 1;
				last;
			}
		}
		last if ($foundone);
	}
}

foreach my $cve (sort keys %CanDBReader::bugzillas) {
	&SMASHData::read_smash_issue($cve);

	if (!defined($SMASHData::state{$cve})) {
		print STDERR "no smash state for $cve?\n" if -t STDERR;
		next;
	}

	if ($SMASHData::state{$cve} eq "Resolved") {
		my @bugzillas = split(/,/,$CanDBReader::bugzillas{$cve});

		my %bugs = map {$_ => 1} @bugzillas;
		@bugzillas = sort keys %bugs;

		foreach my $bug (@bugzillas) {
			if (	defined($CanDBReader::bugzilla_state{$bug}) && (
					($CanDBReader::bugzilla_state{$bug} ne "RESOLVED") &&
					($CanDBReader::bugzilla_state{$bug} ne "CLOSED") &&
					($CanDBReader::bugzilla_state{$bug} ne "VERIFIED")
				)
			) {
				$unclosedbugs{$cve} = $bug;
			}
		}
	}

	# Kernel handling
	# ignore all older than 2020 for now.


	if ($mode eq "kernel") {
		$cve =~ /CVE-(\d*)-/;
		my $year = $1;
		next if ($year < 2020);
	}

	my $refetchsmash = 0;


	if (!defined($SMASHData::pkgstate{$cve})) {
		next;
	}
	my %prods = %{$SMASHData::pkgstate{$cve}};

	my $basescore 		= "unknown";
	my $basevector		= "unknown";
	my $nvdbasescore	= "unknown";
	my $nvdbasevector	= "unknown";

	my $iskernel = 0;
	my $isglibc = 0;
	my $isopenssl = 0;
	my $isinanalysis = 0;

	if (defined($SMASHData::cvssv3{$cve}))  {
		my %entry = %{$SMASHData::cvssv3{$cve}};
		my %score;
		my %nvdscore;

		if (defined($entry{'SUSE'})) {
			%score = %{$entry{'SUSE'}};
			$basescore = $score{'base_score'};
			$basevector = $score{'base_vector'};
		}
		if (defined($entry{'National Vulnerability Database'})) {
			%nvdscore = %{$entry{'National Vulnerability Database'}};
			$nvdbasescore = $nvdscore{'base_score'};
			$nvdbasevector = $nvdscore{'base_vector'};
		}
	}
	# { PROD -> { PKG -> STATE } }

	foreach my $prod (keys %prods) {
		next if ($prod eq "SLES_TERADATA-10-SP3");

		my %pkgstates = %{$prods{$prod}};
		#print STDERR "$prod\n";


		foreach my $pkg (sort keys %pkgstates) {
			my $state = $pkgstates{$pkg};

			next if ($state eq "Not affected");
			# next if ($state eq "Ignore");

			#print STDERR "\t$pkg -> $pkgstates{$pkg}\n";

			$iskernel = 1 if ($pkg =~ /kernel-source$/);
			$isglibc = 1 if ($pkg =~ /glibc/);
			$isopenssl = 1 if ($pkg =~ /openssl-1_1/);
			$isinanalysis = 1 if ($state eq "Analysis");
		}
	}

	my $basenum = 42;
	if ($nvdbasescore  =~ /^(\d+.*)/) {
		$basenum = $1;
	}

	# TEMP
	my $xscore = $basescore;
	if ($xscore eq "unknown") { $xscore = $nvdbasescore; }
	if ($xscore ne "unknown") {
		if (	(($xscore >= 7.0) &&
			 (($SMASHData::severity{$cve} eq "moderate") || ($SMASHData::severity{$cve} eq "low"))
			) && (($SMASHData::state{$cve} ne "Resolved") && ($SMASHData::state{$cve} ne "Ignore"))
		) {
			open(BADSCORE,">>badscore.csv");
			print BADSCORE "$cve,$basescore,$nvdbasescore,$SMASHData::severity{$cve}\n";
			close(BADSCORE);
		}
		if ((($xscore >= 9.0) && (($SMASHData::severity{$cve} ne "critical"))) && (($SMASHData::state{$cve} ne "Resolved") && ($SMASHData::state{$cve} ne "Ignore"))) {
			open(BADSCORE,">>badscore.csv");
			print BADSCORE "$cve,$basescore,$nvdbasescore,$SMASHData::severity{$cve}\n";
			close(BADSCORE);
		}
	}

	if ($iskernel || $isopenssl || $isglibc) {
		if ($basenum >= 7.0) {
			$allcvss7{$cve} = "openssl" if ($isopenssl);
			$allcvss7{$cve} = "kernel" if ($iskernel);
			$allcvss7{$cve} = "glibc" if ($isglibc);
		}
	}

	if ($iskernel || $isopenssl || $isglibc) {
		if ($basescore =~ /^(\d+)/) {
			my $basenum = $1;

			if (	($SMASHData::state{$cve} ne "Resolved") &&
				($SMASHData::state{$cve} ne "Not for us")
			) {
				$cve2cvss{$cve} = $basenum;
			}

			if ($basenum >= 7.0) {
				my $bugs = $CanDBReader::bugzillas{$cve};
				my @bugs = split(/,/,$bugs);
				my %bugs = map { $_ => 1 } @bugs;
				@bugs  = keys %bugs;

				if ($#bugs < 1) {
					#push @nolivepatch,"ERROR: $cve for Kernel has only 1 bug, has packages marked affected, but cvss v3 score $basescore!\n";
					my $cvepkg;

					$cvepkg = "$cve - kernel" if ($iskernel);
					$cvepkg = "$cve - glibc" if ($isglibc);
					$cvepkg = "$cve - openssl" if ($isopenssl);

					if (defined($CanDBReader::bugzilla_priority{$bugs[0]})) {
						$cvepkg .= " bug prio: " . $CanDBReader::bugzilla_priority{$bugs[0]};
					}
					if (defined($CanDBReader::bugzilla_severity{$bugs[0]})) {
						$cvepkg .= " bug sever: " . $CanDBReader::bugzilla_severity{$bugs[0]};
					}
					push @nolivepatch,$cvepkg;
					$refetchsmash = 1;
				}
				#if ($SMASHData::state{$cve} eq "Resolved") {
				#	if ($CanDBReader::bugzilla_state{$bugs[0]} ne "RESOLVED") {
				#		print STDERR "$cve - $bugs[0] state is " . $CanDBReader::bugzilla_state{$bugs[0]} . "\n";
				#	}
				#}
			}
		}

	}

	if ($iskernel && ($basescore ne "unknown") && ($nvdbasescore ne "unknown") && ($basescore ne $nvdbasescore)) {
		# only report if we have one above 7 and one below 7.
		if (	($basescore !~ /^[7-9]/ && $nvdbasescore =~ /^[7-9]/)) {

		#if (	($basescore =~ /^[7-9]/ && $nvdbasescore !~ /^[7-9]/) ||
		#	($basescore !~ /^[7-9]/ && $nvdbasescore =~ /^[7-9]/)) {
			push @differentcvssv3, $cve;
		}
	}
	# different cvssv3 mode for all updates
	if (($mode eq "other") && ($basescore ne "unknown") && ($nvdbasescore ne "unknown") && ($basescore ne $nvdbasescore)) {
		if ($basescore ne $nvdbasescore) {
			push @differentcvssv3, $cve;
		}
	}
	if ($iskernel && ($basescore eq "unknown")) {
		#push @nocvssv3, "ERROR: $cve is not CVSS v3 rated, but for the kernel.\n";
		push @nocvssv3, $cve;
		$refetchsmash = 1;
	}
	if (!$iskernel && ($basescore eq "unknown") && ($SMASHData::state{$cve} ne "Not for us")) {
		#push @nocvssv3, "ERROR: $cve is not CVSS v3 rated, but for the kernel.\n";
		push @nocvssv3other, $cve;
		$refetchsmash = 1;
	}
	if ($iskernel && $isinanalysis) {
		#push @analysis, "ERROR: $cve for kernel, some packages still in analysis.\n";
		push @analysis, $cve;
		$refetchsmash = 1;
	}
	#&SMASHData::read_smash_issue($cve,1) if ($refetchsmash);

}
#close(CVSS);

if ($mode eq "error") {
	print "CVEs with duplicate package assignments\n";
	print join("\n",sort keys %SMASHData::errorcves);
	print "\n";
#	foreach my $cve (keys %SMASHData::errorcves) {
#		&SMASHData::read_smash_issue($cve,1);
#	}
}

if ($mode eq "kernel") {
	if (@nocvssv3) {
		#open(SENDMAIL,"|/usr/bin/mail -R meissner\@suse.de -s 'Kernel issues without CVSSv3 score' security-reports\@suse.de");
		#print SENDMAIL "Following CVEs are for the kernel and have no SUSE CVSSv3 score:\n\t";
		#print SENDMAIL join("\n\t", @nocvssv3) . "\n";
		#close(SENDMAIL);

	}

	if (@nolivepatch) {
		print "Following CVEs are for the kernel/openssl/glibc, have SUSE CVSSv3 score >= 7.0 and have no live patching bug:\n\t";
		print join("\n\t", @nolivepatch) . "\n";
	}

	my @sortedcves = sort { $cve2cvss{$b} - $cve2cvss{$a}}  keys %cve2cvss;

	print "Top 10 CVSS v3 score CVEs which are not yet resolved or notforus (for Marcus to check whether to schedule kernel updates):\n";
	for my $cve (@sortedcves[0..10]) {
		print "\t$cve - $cve2cvss{$cve}\n";
		#&SMASHData::read_smash_issue($cve,1);
	}


	# not looking at this currently
	# if (@analysis) {
	# 	print "Following CVEs are for the kernel and have packages in Analysis (" . $#analysis . "):\n\t";
	# 	print join("\n\t", @analysis) . "\n";
	# }

	if (@differentcvssv3) {
		print "Following CVEs are for the kernel and have different CVSS v3 scores.\n";
		foreach my $cve (sort @differentcvssv3) {
			my %entry = %{$SMASHData::cvssv3{$cve}};
			my %score;
			my $basescore;
			my $basevector;
			my %nvdscore;
			my $nvdbasescore;
			my $nvdbasevector;

			if (defined($entry{'SUSE'})) {
				%score = %{$entry{'SUSE'}};
				$basescore = $score{'base_score'};
				$basevector = $score{'base_vector'};
			}
			if (defined($entry{'National Vulnerability Database'})) {
				%nvdscore = %{$entry{'National Vulnerability Database'}};
				$nvdbasescore = $nvdscore{'base_score'};
				$nvdbasevector = $nvdscore{'base_vector'};
			}
			print "$cve: SUSE $basescore vs NVD $nvdbasescore\n";
			print "\tSUSE $basevector\n";
			print "\tNVD  $nvdbasevector\n";
		}
	}
}

if ($mode eq "other") {
	if (%unclosedbugs) {
		print "Following CVE SMASH States are 'Resolved' but bugs are not closed:\n";
		my @xbugs = ();
		foreach my $cve (sort keys %unclosedbugs) {
			if (!defined($SMASHData::pkgstate{$cve})) {
				print STDERR "no packages assigned for $cve\n" if -t STDERR;
				next;
			}

			print "\t$cve: ";
			
			my @bugzillas = split(/,/,$CanDBReader::bugzillas{$cve});

			my %bugs = map {$_ => 1} @bugzillas;
			@bugzillas = sort keys %bugs;

			foreach my $bug (@bugzillas) {
				my $state = "unknown";
				if (defined($CanDBReader::bugzilla_state{$bug})) {
					$state = $CanDBReader::bugzilla_state{$bug};
				} else {
					print STDERR "no bug state for $bug\n" if -t STDERR;
				}

				if (($state ne "RESOLVED") && ($state ne "CLOSED") && ($state ne "VERIFIED")) {
					print "$bug ($state) ";
					push @xbugs,$bug;
				}
			}
			print "\n";
		}
		print "open the whole list at once: firefox https://bugzilla.suse.com/buglist.cgi?bug_id=" . join(",",sort @xbugs). "\n";

	}
	if (@nocvssv3other) {
		print "Following CVEs are for other CVEs from 2020 and 2021 not marked as 'Not For Us' that have no SUSE CVSSv3 score (" . $#nocvssv3other . "):\n\t";
		print join("\n\t", @nocvssv3other) . "\n";
	}
}

if ($mode eq "cvssv4") {
	my $total = 0;
	my $higher7 = 0;
	my $lower7 = 0;
	my $higher9 = 0;
	my $lower9 = 0;
	my $lower = 0;
	my $higher = 0;
	print "# CVS, CVSS v3 score, CVSS v4 score,compared,lower7,higher7\n";
	foreach my $cve (sort keys %CanDBReader::bugzillas) {
		if (!defined($SMASHData::cvssv4{$cve})) {
			next;
		}
		$total++;
		my $score3 = $SMASHData::cvssv3{$cve}->{'SUSE'}->{'base_score'};
		my $score4 = $SMASHData::cvssv4{$cve}->{'SUSE'}->{'base_score'};
		if ($score3 < $score4) {
			$higher++;
		}
		if ($score4 < $score3) {
			$lower++;
		}
		my $compare = $score3 <=> $score4;
		my $compare4 = 0;
		my $compare7 = 0;
		my $compare9 = 0;
		if (($score3 < 7.0) && ( $score4 >= 7.0)) {
			$compare7 = 1;
			$higher7++;
		}
		if (($score4 < 7.0) && ( $score4 >= 7.0)) {
			$compare7 = -1;
			$lower7++;
		}
		if (($score3 < 9.0) && ( $score4 >= 9.0)) {
			$compare9 = 1;
			$higher9++;
		}
		if (($score4 < 9.0) && ( $score4 >= 9.0)) {
			$compare9 = -1;
			$lower9++;
		}
		print "$cve,$score3,$score4,$compare,$compare7,$compare9\n";
	}

	print "# Total: $total, $higher cvss4 scores higher than v3, $lower cvssv4 scores lower than v3, $higher7 scores went above 7, $lower7 scores went below 7, $higher9 scores went above 9, $lower9 scores went below 9\n";
}

open(ALLSCORES,">allscores.csv");
print ALLSCORES "#CVE,NVD base score,NVD base vector,SUSE score,SUSE base vector,RH score, RH base vector, Amazon score, Amazon base vector\n";

open(CVSSMISMATCH,">mismatch.html");
print CVSSMISMATCH "<table border=1>\n";
print CVSSMISMATCH "<tr><th>CVE</th><th>Source</th><th>Base Score</th></tr>\n";

my $same = 0;
my $notsame = 0;
my $rhsame = 0;
my $rhnotsame = 0;
my $azsame = 0;
my $aznotsame = 0;
my $susehigher = 0;
my $suselower = 0;
my $rhsusehigher = 0;
my $rhsuselower = 0;
my $azsusehigher = 0;
my $azsuselower = 0;

foreach my $cve (sort keys %CanDBReader::bugzillas) {
#	print "$cve - $allcvss7{$cve} - ";

	my $haskernel = 0;
	if (defined($SMASHData::pkgstate{$cve}))  {
		my %prods = %{$SMASHData::pkgstate{$cve}};
		foreach my $prod (keys %prods) {
			my %pkgs = %{$prods{$prod}};

			if (grep(/kernel-so/,keys %pkgs)) {
				$haskernel = 1;
				last;
			}
		}
	}

	next unless ($haskernel);

	if (defined($SMASHData::cvssv3{$cve}))  {
		my %entry = %{$SMASHData::cvssv3{$cve}};
		my %score;
		my $basescore = 42;
		my $basevector = "unknown";

		my %nvdscore;
		my $nvdbasescore;
		my $nvdbasevector = "unknown";

		my %rhscore;
		my $rhbasescore;
		my $rhbasevector = "unknown";

		my %azscore;
		my $azbasescore;
		my $azbasevector = "unknown";

		if (defined($entry{'SUSE'})) {
			%score = %{$entry{'SUSE'}};
			$basescore = $score{'base_score'};
			$basevector = $score{'base_vector'};
		}
		if (defined($entry{'National Vulnerability Database'})) {
			%nvdscore = %{$entry{'National Vulnerability Database'}};
			$nvdbasescore = $nvdscore{'base_score'};
			$nvdbasevector = $nvdscore{'base_vector'};
		}
		if (defined($entry{'Redhat'})) {
			%rhscore = %{$entry{'Redhat'}};
			$rhbasescore = $rhscore{'base_score'};
			$rhbasevector = $rhscore{'base_vector'};
		}

		if (defined($entry{'AMAZON_LINUX'})) {
			%azscore = %{$entry{'AMAZON_LINUX'}};
			$azbasescore = $azscore{'base_score'};
			$azbasevector = $azscore{'base_vector'};
		}

		next if ($basescore eq 42);

		print ALLSCORES "$cve,";
		if (defined($nvdbasescore)) {
			print ALLSCORES "$nvdbasescore,$nvdbasevector,";
		} else {
			print ALLSCORES ",,";
		}
		if (defined($basescore)) {
			print ALLSCORES "$basescore,$basevector,";
		} else {
			print ALLSCORES ",,";
		}
		if (defined($rhbasescore)) {
			print ALLSCORES "$rhbasescore,$rhbasevector";
		} else {
			print ALLSCORES ",,";
		}
		if (defined($azbasescore)) {
			print ALLSCORES "$azbasescore,$azbasevector";
		} else {
			print ALLSCORES ",,";
		}
		print ALLSCORES "\n";

		next unless (defined($basescore));

		next unless (($basescore < 7) && (
			(defined($rhbasescore) && ($rhbasescore >= 7.0)) || (defined($azbasescore) && ($azbasescore >= 7.0)) || (defined($nvdbasescore) && ($nvdbasescore >= 7.0))
		));

		# specific condition

		# next if ($basescore >= 7);
		# next if ($rhbasescore != $nvdbasescore);
		# next if ($rhbasescore < 7);

		my $nvdsym = "";
		if (defined($nvdbasescore)) {
			$same++ if ($basescore eq $nvdbasescore);
			$notsame++ if ($basescore ne $nvdbasescore);
			if ($basescore > $nvdbasescore) {
				$susehigher++;
				$nvdsym = " v";
			} else {
				if ($basescore < $nvdbasescore) {
					$suselower++;
					$nvdsym=" ^";
				}
			}
		}
		my $rhsym = "";

		if (defined($rhbasescore)) {
			$rhsame++ if ($basescore eq $rhbasescore);
			$rhnotsame++ if ($basescore ne $rhbasescore);
			if ($basescore > $rhbasescore) {
				$rhsusehigher++;
				$rhsym = " v";
			} else {
				if ($basescore < $rhbasescore) {
					$rhsuselower++;
					$rhsym = " ^";
				}
			}
		}
		my $azsym = "";
		if (defined($azbasescore)) {
			$azsame++ if ($basescore eq $azbasescore);
			$aznotsame++ if ($basescore ne $azbasescore);
			if ($basescore > $azbasescore) {
				$azsusehigher++;
				$azsym = " v";
			} else {
				if ($basescore < $azbasescore) {
					$azsuselower++;
					$azsym = " ^";
				}
			}
		}

		if (	(defined($nvdbasescore) && ($basescore ne $nvdbasescore))	||
			(defined($rhbasescore) && ($basescore ne $rhbasescore))		||
			(defined($azbasescore) && ($basescore ne $azbasescore))
		) {
			my $bv   = $basevector;
			my $nbv  = $nvdbasevector;
			my $rhbv = $rhbasevector;
			my $azbv = $azbasevector;

			if (defined($nvdbasevector)) {
				$nvdbasevector = "";
			}
			if (defined($rhbasevector)) {
				$rhbasevector = "";
			}
			if (defined($azbasevector)) {
				$azbasevector = "";
			}

			while ($bv =~ /^(.)/) {
				my $firstchar = $1;
				$bv =~ s/^.//;

				if (defined($nbv)) {
					if ($nbv =~ s/^(.)//) {
						if ($firstchar ne $1) {
							$nvdbasevector .= "<strong>$1</strong>";
						} else {
							$nvdbasevector .= $1;
						}
					}
				}
				if (defined($rhbv)) {
					if ($rhbv =~ s/^(.)//) {
						if ($firstchar ne $1) {
							$rhbasevector .= "<strong>$1</strong>";
						} else {
							$rhbasevector .= $1;
						}
					}
				}
				if (defined($azbv)) {
					if ($azbv =~ s/^(.)//) {
						if ($firstchar ne $1) {
							$azbasevector .= "<strong>$1</strong>";
						} else {
							$azbasevector .= $1;
						}
					}
				}
			}
			print CVSSMISMATCH "<tr><td><a href=\"https://smash.suse.de/issue/?q=$cve\">$cve</a></td><td>SUSE<br/>NVD$nvdsym<br/>Red Hat$rhsym<br/>Amazon$azsym</td><td>$basescore $basevector<br/>$nvdbasescore $nvdbasevector <br/>$rhbasescore $rhbasevector<br/>$azbasescore $azbasevector</td></tr>\n";
		}
	}
}
close(ALLSCORES);
print CVSSMISMATCH "</table>\n";
print CVSSMISMATCH "Script Mode: Only kernel, only SUSE < 7 and ONE OF the others >= 7.0.<br/><br/>\n";
print CVSSMISMATCH "NVD Same scores $same, Not same scores $notsame.<br/>\n";
print CVSSMISMATCH "NVD SUSE score higher $susehigher, SUSE score lower $suselower.<br/>\n";
print CVSSMISMATCH "RH Same scores $rhsame, Not same scores $rhnotsame.<br/>\n";
print CVSSMISMATCH "RH SUSE score higher $rhsusehigher, SUSE score lower $rhsuselower.<br/>\n";
print CVSSMISMATCH "AZ Same scores $azsame, Not same scores $aznotsame.<br/>\n";
print CVSSMISMATCH "AZ SUSE score higher $azsusehigher, SUSE score lower $azsuselower.<br/>\n";
close(CVSSMISMATCH);

print "This report was generated by $0 from https://gitlab.suse.de/security/cve-database.git at meissner\@maintenance.suse.de\n";
