#!/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 CVEListReader;
require SMASHData;
require UpdateInfoReader;
UpdateInfoReader->import_product_updates();


&SMASHData::read_all_cached_issues();

my $match = "^SUSE.Linux.Enterprise.Server.*12-SP[23]";

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


my %cve2cvss = ();
my %cve2cvssnvd = ();

my %allcvss7 = ();

my @allcves = ();

foreach my $patchid (keys %{$UpdateInfoReader::patches{"SUSE Linux Enterprise Server 12 SP2-BCL"}}) {
	next if ($UpdateInfoReader::patchissued{$patchid} <= 1618908907);	# the highest 12-sp2-ltss entry

	foreach my $cve (keys %{$UpdateInfoReader::patchreferences{$patchid}}) {
		next unless ($cve =~ /CVE/);

		my $prodstate = $UpdateInfoReader::products{$cve};
		unless (defined($prodstate)) {
			print STDERR "BCL: $cve in patches , but not in products?\n";
			next;
		}
		unless (defined($prodstate->{"SUSE Linux Enterprise Server 12 SP2-BCL"})) {
			print STDERR "BCL: $cve in patches , but no product state?\n";
			next;
		}
		unless (defined($prodstate->{"SUSE Linux Enterprise Server 12 SP2-BCL"}->{"srcpackages"})) {
			print STDERR "BCL: $cve in patches , but no src packages in product state?\n";
			next;
		}

		my %srcpackages = %{$prodstate->{"SUSE Linux Enterprise Server 12 SP2-BCL"}->{"srcpackages"}};

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

			my $basenum = -1;
			if (defined($entry{'SUSE'})) {
				%score = %{$entry{'SUSE'}};
				my $basescore = $score{'base_score'};
				my $basevector = $score{'base_vector'};
				if ($basescore  =~ /^(\d+.*)/) {
					$cve2cvss{$cve} = $1;
					if ($cve2cvss{$cve} >= 7) {
						push @allcves,$cve;

						foreach my $pkg (keys %srcpackages) {
							next if ($pkg =~ /kernel-source/);	# we keep kernel-default
							next if ($pkg =~ /kernel-syms/);	# we keep kernel-default
							next if ($pkg =~ /python-libxml2/);	# we keep libxml2
							next if ($pkg =~ /MozillaFirefox-branding-SLE/);	# we keep MozillaFirefox itself
							next if ($pkg =~ /openssh-askpass-gnome/);	# we keep openssh
							next if ($pkg =~ /python-base/);	# we keep python
							next if ($pkg =~ /python-doc/);	# we keep python
							next if ($pkg =~ /python3-base/);	# we keep python
							next if ($pkg =~ /python36/);	# not in 12-sp2 itself
							next if ($pkg =~ /postgresql$/);	# wrapper package
							$allcvss7{$cve}->{$pkg} = 1;
							print STDERR "BCL $cve $pkg\n" if -t STDERR;
						}
					}
				} else {
					$cve2cvss{$cve} = -1;
				}
			}
			if (defined($entry{'National Vulnerability Database'})) {
				%nvdscore = %{$entry{'National Vulnerability Database'}};
				my $nvdbasescore = $nvdscore{'base_score'};
				my $nvdbasevector = $nvdscore{'base_vector'};
				my $nvdbasenum = -1;
				if ($nvdbasescore  =~ /^(\d+.*)/) {
					$cve2cvssnvd{$cve} = $1;
					if (($cve2cvssnvd{$cve} >= 7.0) && (!defined($cve2cvss{$cve}) || ($cve2cvss{$cve} == -1))) {
						print STDERR "SUSE UNRATED $cve , NVD score $nvdbasenum\n";
					}
				}
			}
		}
	}
}


#open(CVSS,">allreports.txt");
foreach my $cve (sort keys %CanDBReader::bugzillas) {
	&SMASHData::read_smash_issue($cve);

	# embargoed flag is not cleared after going public, check if we have an advisory listed or a note.
	if (	($CanDBReader::embargoed{$cve} || $SMASHData::embargoedcves{$cve}) &&
		!defined($CanDBReader::advisoryids{$cve}) &&
		!defined($CanDBReader::note{$cve})
	) {
		next;
	}

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

	#$cve =~ /CVE-(\d*)-/;
	#my $year = $1;
	#next if ($year < 2019);

	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;

	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 } }

	my %foundone = ();

	my %fixedinsp2 = ();
	my %sp2notaffected = ();
	foreach my $prod (keys %prods) {
		#print "$cve - $prod\n";
		next unless (	($prod =~ /^SUSE.Linux.Enterprise.Server.*12.SP[23]/i)  ||
				($prod =~ /^SLES-LTSS-SAP/i)  ||
				($prod =~ /^SLES-LTSS-ERICSSON/i));
		print STDERR "$cve - $prod - ok\n" if -t STDERR;
		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};

			# special hack, as GEHC does not have those, are only interested in kernel-source.
			next if ($pkg eq "kernel-source-azure");
			next if ($pkg eq "kernel-source-rt");
			next if ($pkg eq "kernel-source");
			next if ($pkg =~ /kgraft-patch/);

			# as we have some live SP2 products still, skip CVEs declared "not affected" or similar.
			if (	(	($state eq "Not affected") ||
					($state eq "Already fixed") ||
					($state eq "Ignore")
				) && (($prod =~ /SP2.LTSS/) ||  ($prod =~ /SP2$/))) {
				print STDERR "$cve - NOT AFFECTED!\n" if -t STDERR;
				$sp2notaffected{$pkg} = 1;
				next;
			}
			next if ($state eq "Not affected");
			next if ($state eq "Already fixed");
			next if ($state eq "Unsupported"); # Unsupported means was affected previously, now not affected.
			next if ($state eq "Ignore");

			next if ($state eq "Analysis");	# FIXME

			print STDERR "$cve - $prod - $pkg - $state\n" if -t STDERR;
# also getting these as SP2 ... they should not be ignored.
#CVE-2021-3497 - SUSE Linux Enterprise Server 12 SP2 LTSS ERICSSON - gstreamer-plugins-good - Affected
#CVE-2021-3497 - SUSE Linux Enterprise Server 12 SP2 BCL - gstreamer-plugins-good - Affected
#CVE-2021-3497 - SUSE Linux Enterprise Server 12 SP2 LTSS SAP - gstreamer-plugins-good - Affected
			if (($state eq "Released") && ((($prod =~ /SP2.LTSS$/) ||  ($prod =~ /SP2$/)) && ($prod !~ /ERICSSON/) && ($prod !~ /SAP/))) {
				# we released it for SP2, ignore it...
				print STDERR "$cve in $pkg RELEASED for 12 SP2 LTSS already (for $prod)\n" if -t STDERR;
				$fixedinsp2{$pkg} = 1;
				next;
			}

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

			$allcvss7{$cve}->{$pkg} = 1;
			$foundone{$pkg} = 1;
		}
	}
	#&SMASHData::read_smash_issue($cve,1);

	foreach my $pkg (keys %foundone) {
		print STDERR "$cve - checking $pkg...\n" if -t STDERR;
		if ($fixedinsp2{$pkg} || $sp2notaffected{$pkg}) {
			print STDERR " pruning $pkg ($fixedinsp2{$pkg} / $sp2notaffected{$pkg})\n" if -t STDERR;
			delete $foundone{$pkg};
			delete $allcvss7{$cve}->{$pkg};
		}
	}
	print STDERR Dumper(\%foundone) if -t STDERR;
	next unless (%foundone);

	my $nvdbasenum = -1;
	if ($nvdbasescore  =~ /^(\d+.*)/) {
		$nvdbasenum = $1;
	}
	$cve2cvssnvd{$cve} = $nvdbasenum;

	my $basenum = -1;
	if ($basescore  =~ /^(\d+.*)/) {
		$basenum = $1;
	}
	if ($basenum < 7.0) { # delete it again
		if (($nvdbasenum >= 7.0) && ($basenum == -1)) {
			print STDERR "SUSE UNRATED $cve , NVD score $nvdbasenum\n";
		}
		delete $allcvss7{$cve};
		next;
	}
	$cve2cvss{$cve} = $basenum;

	push @allcves,$cve;

	#&SMASHData::read_smash_issue($cve,1) if ($refetchsmash);
}

#unify array
my %xxcves = map { $_ => 1} @allcves;
@allcves = sort keys %xxcves;

sub bugzillacmp($$) {
	my ($a,$b) = @_;
	die "no bug for $a" if (!defined($CanDBReader::bugzillas{$a}));
	die "no bug for $b" if (!defined($CanDBReader::bugzillas{$b}));
	my @abugs = sort split(/,/,$CanDBReader::bugzillas{$a});
	my @bbugs = sort split(/,/,$CanDBReader::bugzillas{$b});
	return  $abugs[$[] <=> $bbugs[$[];
}

#my @sortedcves = sort bugzillacmp @allcves;
my @sortedcves = sort {$CanDBReader::firstdate{$a} cmp $CanDBReader::firstdate{$b}}  @allcves;

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$year += 1900;
$mon += 1;

my $fn = sprintf("gehc-cvss7-list-%04d-%02d-%02d.csv",$year,$mon,$mday);
open(ATTACHMENT,">$fn");
print ATTACHMENT "# first-bug-seen-date,CVE,SUSE-CVSS3-base-score,NVD-CVSS3-base-score,first-bug-with-this-CVE,sourcepackage,URL,Description\n";
for my $cve (@sortedcves) {
	if (!defined($CanDBReader::bugzillas{$cve})) {
		print STDERR "NO BUGZILLA for $cve\n";
		next;
	}

	my @bugs = sort split(/,/,$CanDBReader::bugzillas{$cve});
	my $firstbug = $bugs[0];

	#
	# Only write out issues after 20210101
	#
	next if ($CanDBReader::firstdate{$cve} < 20210101);

	my $desc = get_description($cve);

	$desc =~ s/"/\\"/g;
	$desc =~ s/\n/ /g;	# try filtering linebreaks.

	foreach my $pkg (sort keys %{$allcvss7{$cve}}) {
		print ATTACHMENT "$CanDBReader::firstdate{$cve},$cve,$cve2cvss{$cve},$cve2cvssnvd{$cve},$firstbug,$pkg,https://www.suse.com/security/cve/$cve,\"$desc\"\n";
	}
	#read_smash_issue($cve,1);
}
close(ATTACHMENT);
#
open(SENDMAIL,"|/usr/bin/mail -R meissner\@suse.de -s 'weekly GEHC CVSS >= 7.0 report' -a $fn TVance\@suse.com,AnDavis\@suse.com,meissner\@suse.de");
#open(SENDMAIL,"|/usr/bin/mail -R meissner\@suse.de -s 'weekly GEHC CVSS >= 7.0 report' -a $fn meissner\@suse.de");
print SENDMAIL "This report was generated by $0 from https://gitlab.suse.de/security/cve-database.git at securitybot\@maintenance.suse.de\n";
print SENDMAIL ".\n";
close(SENDMAIL);
