#!/usr/bin/perl -w
use strict;

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

use DateTime;

require CanDBReader;
require ModuleContained;
require UpdateInfoReader;
UpdateInfoReader->import_product_updates();
require SMASHData;

my %products = (
	'sles12-sp2' => 'SUSE Linux Enterprise Server 12 SP2',
	'sles12-sp2-ltss' => 'SUSE Linux Enterprise Server 12 SP2-LTSS',
	'sles11-sp4-ltss'   => 'SUSE Linux Enterprise Server 11 SP4-LTSS',
#	'sles11-sp1-td'   => 'SUSE Linux Enterprise Server 11 SP1-TERADATA',
	'sles11-sp3'   => 'SUSE Linux Enterprise Server 11 SP3',
	'sles11-sp3-td'   => 'SUSE Linux Enterprise Server 11 SP3-TERADATA',
	'sles12-sp3-td'   => 'SUSE Linux Enterprise Server 12 SP3-TERADATA',
	'sles12-sp3'   => 'SUSE Linux Enterprise Server 12 SP3',
#	'sles12-sp3-ltss'   => 'SUSE Linux Enterprise Server 12 SP3-LTSS',
#	'sles12-sp4'   => 'SUSE Linux Enterprise Server 12 SP4',
	'sles12-sp5'   => 'SUSE Linux Enterprise Server 12 SP5',
	'sle15-sp1'	=> '15 SP1',
	'sle15-sp2'	=> '15 SP2',
	'sle15-sp3'	=> '15 SP3',
	'sle15-sp4'	=> '15 SP4$',
	'sle15-sp4-td'	=> '15 SP4.*TERADATA',
#	'leap-15.2'	=> '15.2$',
#	'all'		=> '.',
#	'sle-all'	=> '^SUSE.*.',
#	'sle11-all'	=> '^SUSE.*.11.*',
#	'sle12-all'	=> '^SUSE.*.12.*',
#	'sle15-all'	=> '^SUSE.*.15.*',
#	'sle15-sp2-base'   => 'SUSE Linux Enterprise Module for Basesystem 15 SP2',
#	'sle15-sp1-base'   => 'SUSE Linux Enterprise Module for Basesystem 15 SP1',
#	'chost15-sp2-aws'   => 'Image SLES15-SP2-CHOST-BYOS-EC2',
#	'chost15-sp1-aws'   => 'Image SLES15-SP1-CHOST-BYOS-EC2',
#	'chost15-sp1-azure'   => 'Image SLES15-SP1-CHOST-BYOS-Azure',
#	'chost15-sp1-gce'   => 'Image SLES15-SP1-CHOST-BYOS-GCE',
#	'chost15-sp1-openstack'   => 'Image SLES15-SP1-CHOST-BYOS-OpenStack',
);


my %cve2packages = ();
my %cve2rating = ();
my %cve2time = ();
my %cve2epoch = ();

sub
cvsscmp($$) {
	my ($a,$b) = @_;
	my @a = @{$a};
	my @b = @{$b};
	print STDERR "comparing " . $a[2] . " and " . $b[2] . "\n";
	return $b[2] cmp $a[2];
}

sub
getscore($) {
	my ($cve) = @_;

	my $basescore = "unknown";
	die "cve is $cve in getscore " unless ($cve =~ /^CVE/);
	&SMASHData::read_smash_issue($cve);

	if (defined($SMASHData::cvssv3{$cve}))  {
		my %entry = %{$SMASHData::cvssv3{$cve}};
		my %score;
		if (defined($entry{'SUSE'})) {
			%score = %{$entry{'SUSE'}};
			$basescore = "$score{'base_score'} (SUSE)";
		} else {
			if (defined($entry{'National Vulnerability Database'})) {
				%score = %{$entry{'National Vulnerability Database'}};
				$basescore = "$score{'base_score'}";
			}
		}
	}
	return $basescore;
}

sub
get_source_by_smash($$) {
	my ($prod,$cve) = @_;

	&SMASHData::read_smash_issue($cve);
	return () unless ($SMASHData::pkgstate{$cve});

	my %src = ();

	foreach my $xprod (keys %{$SMASHData::pkgstate{$cve}}) {
		next unless ($xprod =~ /$prod/);
		foreach my $src (keys %{$SMASHData::pkgstate{$cve}->{$xprod}}) {
			$src{$src}=1;
		}
	}
	return sort keys %src;
}

my $countedcvss = ();	# product -> { absolute(nr) or "unknown" -> count }
my $productcves = ();	# product -> { cve-> 1 }

foreach my $codename (keys %products) {
	print "$products{$codename}\n";

	my %patches = ();
	foreach my $xprod (grep (/$products{$codename}/,keys %UpdateInfoReader::patches)) {

		next if ($xprod =~ /Development-Tools-OBS/);

		my %xpatches = %{$UpdateInfoReader::patches{$xprod}};
		foreach my $patch (keys %xpatches) {
			next if ($patch =~ /Development-Tools-OBS/);
			$patches{$patch} = 1;
		}
	}
	# my %patches = %{$UpdateInfoReader::patches{$products{$codename}}};

	open(PATCHES,">$codename-patches.csv");
	%cve2packages = ();
	%cve2time = ();

	my %havepatch = ();
	foreach my $patch (keys %patches) {
		next if ($UpdateInfoReader::patchinqa{$patch});

		next if ($patch =~ / GA /);
		next if ($UpdateInfoReader::patchtitle{$patch} =~ / GA /);

		my $intpatch = $patch;

		$intpatch =~ s/^SUSE-.*-(\d+-\d+)$/SUSE-$1/;;
		next if ($havepatch{$intpatch});

		#$havepatch{$intpatch} = 1;

		my $issued = $UpdateInfoReader::patchissued{$patch};
		my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($issued);

		$year+= 1900;
		$mon ++;
		my $xtime = sprintf("%d-%02d-%02d",$year,$mon,$mday);
		my @references = sort grep (/CVE/, keys %{$UpdateInfoReader::patchreferences{$patch}});

		my %packages = %{$UpdateInfoReader::patchpackages{$patch}};
		my @packages = ();

		foreach my $pkg (sort keys %packages) {
			push @packages, "$pkg-$packages{$pkg}",
		}

		next unless (@references);

		my $basescore = 0;

		foreach my $cve (@references) {
			if (!defined($cve2packages{$cve})) {
				$cve2packages{$cve}  = join(",", @packages);
			} else {
				$cve2packages{$cve} .= "," . join(",", @packages);
			}
			$cve2time{$cve}		= $xtime;
			if (defined($cve2epoch{$cve})) {
				# if already released, update it only if "older" ... we want the first released update as indicator
				$cve2epoch{$cve}	= $issued if ($cve2epoch{$cve} > $issued)
			} else {
				$cve2epoch{$cve}	= $issued;
			}
			$cve2rating{$cve} = $UpdateInfoReader::patchseverity{$patch};
			#print "\t$xtime:$cve:" . join(",", @packages) . "\n";
			my $newscore = getscore($cve);
			if ($newscore ne "unknown") {
				if ($basescore < $newscore) {
					$basescore = $newscore;
				}
			}
		}
		print PATCHES "$intpatch,$xtime,$basescore,\"" . join(",", @references) . "\",\"" . join(",", @packages) . "\"\n";
	}
	close(PATCHES);
	open(CVES,">$codename-cves.csv");
	print CVES "#cve,opened,daydiff,suse score,packages\n";
	foreach my $cve (keys %cve2packages) {
		my $basescore = "unknown";
		my $nvdbasescore = "unknown";
		&SMASHData::read_smash_issue($cve);
		if (defined($SMASHData::cvssv3{$cve}))  {
			my %entry = %{$SMASHData::cvssv3{$cve}};
			my %score;
			if (defined($entry{'National Vulnerability Database'})) {
				%score = %{$entry{'National Vulnerability Database'}};
				$nvdbasescore = $score{'base_score'};
			}
			if (defined($entry{'SUSE'})) {
				%score = %{$entry{'SUSE'}};
				$basescore = $score{'base_score'};
			} else {
				if (defined($entry{'National Vulnerability Database'})) {
					%score = %{$entry{'National Vulnerability Database'}};
					$basescore = $score{'base_score'};
				}
			}
		}
		#next if ($basescore =~ /^[0-6]/);
		#next if ($basescore =~ /^unknown/);
		my $integerbase;

		if ($basescore =~ /^(\d*)/) {
			$integerbase = $1;
		} else {
			$integerbase = $basescore;
		}

#		foreach my $xprod (grep (/$products{$codename}/,keys %UpdateInfoReader::patches)) {
#			next if ($xprod =~ /Development-Tools-OBS/);
#
#			$productcves->{$xprod}->{$cve} = 1;
#		}

		if (!defined($CanDBReader::firstdate{$cve})) {
			print STDERR "no bug for $cve, skipping.\n";
			next;
		}

		my ($year,$month,$day) = ($CanDBReader::firstdate{$cve} =~ /(\d\d\d\d)(\d\d)(\d\d)/);
		print STDERR "FIRST: $cve: $CanDBReader::firstdate{$cve}, year $year month $month day $day\n";
		my $ft = DateTime->new( year => $year, month => $month, day => $day);

		my $diff = ($cve2epoch{$cve} - $ft->epoch)/24/3600;

                #print STDERR "SECOND: $cve: $cve2time{$cve}\n";
		#print STDERR "DIFF: $diff\n";

		my @pkgs = ();

		my $haveone = 0;
		foreach my $src (get_source_by_smash($products{$codename},$cve)) {
			#printf CVES ("$cve,$CanDBReader::firstdate{$cve},%d,$basescore,$nvdbasescore,$src\n", $diff);
			push @pkgs,$src;
			$haveone = 1;
		}
		if (!$haveone) {
			#printf CVES ("$cve,$CanDBReader::firstdate{$cve},%d,$basescore,$nvdbasescore,\"$cve2packages{$cve}\"\n", $diff);
			push @pkgs,"\"$cve2packages{$cve}\"";
		}
		if ($#pkgs > 0) {
			printf CVES ("$cve,$CanDBReader::firstdate{$cve},%d,$basescore,\" " . join(",",@pkgs) . "\"\n", $diff);
		} else {
			printf CVES ("$cve,$CanDBReader::firstdate{$cve},%d,$basescore," . join(",",@pkgs) . "\n", $diff);
		}
	}
	close(CVES);
	open(HTML,">$codename-cves.html.new");
	print HTML "<table border=1>\n";
	print HTML "<tr><th>CVE</th><th>Days</th><th>CVSS v3.1</th><th>Rating</th><th>Packages</th></tr>\n";
	my @records = ();
	foreach my $cve (keys %cve2packages) {
		my $basescore = "unknown";
		die "cve is $cve in cve.html loop" unless ($cve =~ /^CVE/);
		&SMASHData::read_smash_issue($cve);

		if (defined($SMASHData::cvssv3{$cve}))  {
			my %entry = %{$SMASHData::cvssv3{$cve}};
			my %score;
			if (defined($entry{'SUSE'})) {
				%score = %{$entry{'SUSE'}};
				$basescore = $score{'base_score'};
			} else {
				if (defined($entry{'National Vulnerability Database'})) {
					%score = %{$entry{'National Vulnerability Database'}};
					$basescore = $score{'base_score'};
				}
			}
		}
		#next if ($basescore =~ /^[0-6]/);
		#next if ($basescore =~ /^unknown/);

		if (!defined($CanDBReader::firstdate{$cve})) {
			print STDERR "no bug for $cve, skipping.\n";
			next;
		}
		my ($year,$month,$day) = ($CanDBReader::firstdate{$cve} =~ /(\d\d\d\d)(\d\d)(\d\d)/);
		my $ft = DateTime->new( year => $year, month => $month, day => $day);

		my $diff = ($cve2epoch{$cve} - $ft->epoch)/24/3600;

		my @record = ($cve,sprintf("%d",$diff),$basescore,$cve2rating{$cve},$cve2packages{$cve});

		push @records, \@record;

	}
	foreach my $record (sort cvsscmp @records) {
		my ($cve,$diff,$basescore,$rating,$packages) = @{$record};
		printf HTML "<tr><td><a href=\"https://www.suse.com/security/cve/$cve\">$cve</a></td><td>$diff</td><td>$basescore</td><td>$rating</td><td>$packages</td></tr>\n";
	}
	print HTML "</table>\n";
	close(HTML);
	if (system("diff -u $codename-cves.html $codename-cves.html.new")) {
		unlink("$codename-cves.html");
		rename("$codename-cves.html.new","$codename-cves.html");
	} else {
		unlink("$codename-cves.html.new");
	}
}

open(EVERYTHING,">everything.csv");
foreach my $product (keys %UpdateInfoReader::patches) {
	my %patches = %{$UpdateInfoReader::patches{$product}};

	my %havepatch = ();
	foreach my $patch (keys %patches) {
		next if ($UpdateInfoReader::patchinqa{$patch});

		next if ($patch =~ / GA /);
		next if ($UpdateInfoReader::patchtitle{$patch} =~ / GA /);

		my $intpatch = $patch;

		$intpatch =~ s/^SUSE-.*-(\d+-\d+)$/SUSE-$1/;;
		next if ($havepatch{$intpatch});

		$havepatch{$intpatch} = 1;

		my @references = sort grep (/CVE/, keys %{$UpdateInfoReader::patchreferences{$patch}});

		foreach my $cve (@references) {
			foreach my $xprod ($product,contained($product)) {
				$productcves->{$xprod}->{$cve} = 1;
			}

			my $basescore = "unknown";
			my $nvdbasescore = "unknown";
			die "cve is $cve in $patch" unless ($cve =~ /^CVE/);
			&SMASHData::read_smash_issue($cve);
			if (defined($SMASHData::cvssv3{$cve}))  {
				my %entry = %{$SMASHData::cvssv3{$cve}};
				my %score;
				if (defined($entry{'National Vulnerability Database'})) {
					%score = %{$entry{'National Vulnerability Database'}};
					$nvdbasescore = $score{'base_score'};
				}
				if (defined($entry{'SUSE'})) {
					%score = %{$entry{'SUSE'}};
					$basescore = $score{'base_score'};
				} else {
					if (defined($entry{'National Vulnerability Database'})) {
						%score = %{$entry{'National Vulnerability Database'}};
						$basescore = $score{'base_score'};
					}
				}
			}
			my $integerbase;

			print EVERYTHING "$product,$cve,$basescore\n";
		}
	}
}
close(EVERYTHING);

my $rangecvss;
foreach my $product (keys %{$productcves}) {
	foreach my $cve (keys %{$productcves->{$product}}) {
		print STDERR "$product $cve\n";
		my $basescore = "unknown";
		my $nvdbasescore = "unknown";
		die "cve is $cve in productcves loop" unless ($cve =~ /^CVE/);
		&SMASHData::read_smash_issue($cve);
		if (defined($SMASHData::cvssv3{$cve}))  {
			my %entry = %{$SMASHData::cvssv3{$cve}};
			my %score;
			if (defined($entry{'National Vulnerability Database'})) {
				%score = %{$entry{'National Vulnerability Database'}};
				$nvdbasescore = $score{'base_score'};
			}
			if (defined($entry{'SUSE'})) {
				%score = %{$entry{'SUSE'}};
				$basescore = $score{'base_score'};
			} else {
				if (defined($entry{'National Vulnerability Database'})) {
					%score = %{$entry{'National Vulnerability Database'}};
					$basescore = $score{'base_score'};
				}
			}
		}
		my $integerbase;

		if ($basescore =~ /^(\d\d*)/) {	# 7.8 -> 7 (also handle 0, 10, but not "unknown")
			$integerbase = $1;
		} else {
			$integerbase = $basescore;
		}
		$countedcvss->{$product}->{$integerbase} += 1;	# product -> { absolute(nr) or "unknown" -> count }

		# do the common CVE ranges
		my $rangebase = $basescore;

		$rangebase = 0 if  ($integerbase < 4);
		$rangebase = 4 if (($integerbase >= 4) && ($integerbase < 7));
		$rangebase = 7 if (($integerbase >= 7) && ($integerbase < 9));
		$rangebase = 9 if  ($integerbase >= 9);
		$rangebase = "unknown" if ($integerbase eq "unknown");

		my ($year,$month,$day) = ($CanDBReader::firstdate{$cve} =~ /(\d\d\d\d)(\d\d)(\d\d)/);
		if (!defined($year)) {
			if ($cve =~ /CVE-(\d*)-/) {
				$year = $1;
			}
		}

		$rangecvss->{$product}->{$year}->{$rangebase} += 1;	# product -> { year -> { absolute(nr) or "unknown" -> count }}
	}
}

open(ALLCVSS,">allcvss.cvs");
print ALLCVSS "# product or module,0,1,2,3,4,5,6,7,8,9,10,unknown\n";
foreach my $product (sort keys %{$countedcvss}) {
	my %entry = %{$countedcvss->{$product}};
	print ALLCVSS "$product,";
	for (my $i = 0; $i<11;$i++) {
		printf ALLCVSS "%d,",$entry{$i};
	}
	printf ALLCVSS "%d\n",$entry{"unknown"};
}
close(ALLCVSS);
open(RANGECVSS,">rangecvss.cvs");
print RANGECVSS "# product or module,0,4,7,9,unknown\n";
foreach my $product (sort keys %{$rangecvss}) {
	my %yentry = %{$rangecvss->{$product}};
	foreach my $year (sort keys %yentry) {
		my %entry = %{$yentry{$year}};
		print RANGECVSS "$product,$year,$entry{0},$entry{4},$entry{7},$entry{9},$entry{unknown}\n";
	}
}
close(RANGECVSS);
