#!/usr/bin/perl -w
use strict;
use CGI;
my $cgi = new CGI;

use DateTime;

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

# count how much CVSS scores we will refresh...
my $refreshcount = 50;

use POSIX qw/strftime setlocale LC_TIME/;

require CanDBReader;
require CVEListReader;
require SMASHData;
require UpdateInfoReader;

UpdateInfoReader->import_product_updates();
UpdateInfoReader->import_images();

require RancherCVE;
require ModuleContained;
require PInt;
require CPE;
require Products;

# map smash package states to bootstrap alerts
my 	%bootstrapmap = (
	'Already fixed'	=> 'alert-success',
	'Released'	=> 'alert-success',
	'Not affected'	=> 'alert-success',

	'Ignore'	=> 'alert-info',
	'Unsupported'	=> 'alert-info',
	'Analysis'	=> 'alert-info',
	'Ask maintainer'=> 'alert-info',
	'Won\'t fix'	=> 'alert-info',

	'Affected'	=> 'alert-danger',

	'In progress'	=> 'alert-warning',
);

my @changedpages = ();


# We get bnc#xxxxxxx references for lots of issues, remap them to CVE
# FIXME: nice O(N^2) algorithm here ...
foreach my $id (sort keys %SMASHData::cvss) {
	next if ($id =~ /CVE-/);
	if ($id =~ /bnc#(\d*)/) {
		my $nr = $1;
		foreach my $cve (sort keys %CanDBReader::bugzillas) {
			if (grep(/$nr/,$CanDBReader::bugzillas{$cve})) {
				my %current = ();
				if (defined($SMASHData::cvss{$cve})) {
					%current = %{$SMASHData::cvss{$cve}};
				}
				my %xx = %{$SMASHData::cvss{$id}};
				foreach my $key (keys %xx) {
					$current{$key} = $xx{$key};
				}
				$SMASHData::cvss{$cve} = \%current;
				delete $SMASHData::cvss{$id};
				print STDERR "cvss merger: merged $id into $cve\n";
				last;
			}
		}
	}
}

foreach my $id (sort keys %SMASHData::cvssv3) {
	next if ($id =~ /CVE-/);
	if ($id =~ /bnc#(\d*)/) {
		my $nr = $1;
		print STDERR "cvssv3: need to remap $nr\n";

		foreach my $cve (sort keys %CanDBReader::bugzillas) {
			if (grep(/$nr/,$CanDBReader::bugzillas{$cve})) {
				my %current = ();
				if (defined($SMASHData::cvssv3{$cve})) {
					%current = %{$SMASHData::cvssv3{$cve}};
				}
				my %xx = %{$SMASHData::cvssv3{$id}};
				foreach my $key (keys %xx) {
					$current{$key} = $xx{$key};
				}
				$SMASHData::cvssv3{$cve} = \%current;
				delete $SMASHData::cvssv3{$id};
				print STDERR "cvss merger: merged $id into $cve\n";
				last;
			}
		}
	}
}


sub get_cvss($) {
	my ($issue) = @_;
	my $cvss = &SMASHData::get_cvss_issue($issue);

	if (defined($cvss)) {
		return $cvss;
	}
	if ($issue =~ /CVE-/) {
		foreach my $bug (split(/,/,$CanDBReader::bugzillas{$issue})) {
			my $bugname = "bnc#$bug";
			$cvss = &SMASHData::get_cvss_issue($bugname);
			if (defined($cvss)) {
				print STDERR "cvss2: Remapped $issue to $bugname\n";
				return $cvss;
			}
		}
	}
	return undef;
}

foreach my $id (sort keys %SMASHData::severity) {
	next if ($id =~ /CVE-/);
	if ($id =~ /bnc#(\d*)/) {
		my $nr = $1;
		foreach my $cve (sort keys %CanDBReader::bugzillas) {
			next if (defined($SMASHData::severity{$cve}));
			if (grep(/$nr/,$CanDBReader::bugzillas{$cve})) {
				$SMASHData::severity{$cve} = $SMASHData::severity{$id};
				delete $SMASHData::severity{$id};
				print STDERR "severity merger: rating $SMASHData::severity{$cve} merged $id into $cve\n" if (-t STDERR);
				last;
			}
		}
	}
}


my $cvedir = "$cverepobase/cve/";
my @cves = ();
if (%CanDBReader::allcans) { # superflous, but avoid single use warning
	@cves = sort keys %CanDBReader::allcans;
}

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

my %todaypublic = ();

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

	# if we released stuff, embargoed no longer triggers
	if (defined($CanDBReader::advisoryids{$cve})		||
	    defined($CanDBReader::note{$cve})			||
	    defined($UpdateInfoReader::products{$cve})
	) {
		return 1;
	}

	# if we did not yet release stuff, and the CVE is tagged embargoed, do not generate a page.
	if (defined($SMASHData::embargoedcves{$cve})) {
		print STDERR "$cve is embargoed\n" if (-t STDERR);

		return 0 if (!defined($CanDBReader::crds{$cve}));

		my $crd = $CanDBReader::crds{$cve};

		return 0 unless ($crd =~ /(\d\d\d\d)(\d\d)(\d\d)/);

		my $crdyear = $1;
		my $crdmonth = $2;
		my $crdmday = $3;

		print STDERR "$cve embargo compare today $year/$mon/$mday with CRD $crdyear/$crdmonth/$crdmday\n";

		if (	($crdyear == $year) && (
				(($crdmonth == $mon) && ($mday >= $crdmday)) ||
				($mon > $crdmonth)
			)
		) {
			print STDERR "$cve goes public today!\n";
			$todaypublic{$cve} = 1;
			return 1;
		}
		return 0;
	}
	if (defined($CanDBReader::embargoed{$cve})) {
		print STDERR "$cve is embargoed internally, proceeding\n";
		warn "$cve potentiall embargoed" if ($cve =~ /2021/);
		warn "$cve potentiall embargoed" if ($cve =~ /2022/);
	}

	# generate pages if we have stuff in QA, or have bugzillas
	if (defined($CanDBReader::bugzillas{$cve}) || defined($UpdateInfoReader::productsinqa{$cve}) || defined($SMASHData::pkgstate{$cve})) {
		return 1;
	} else {
		return 0;
	}
}

sub cvesortfun($$) {
	my ($xa,$xb) = @_;
	my ($ya,$na,$yb,$nb);
	if ($xa =~ /-(\d*)-(\d*)$/) {
		$ya = $1; $na = $2;
		#print "$xa - $ya / $na\n";
	} else {
		print "$xa did not parse\n";
		$ya = 9999;
	}
	if ($xb =~ /-(\d*)-(\d*)$/) {
		$yb = $1; $nb = $2;
		#print "$xb - $yb / $nb\n";
	} else {
		print "$xb did not parse\n";
		$yb = 9999;
	}
	return $ya <=> $yb if ($ya != $yb);
	return $na <=> $nb;
}


my %printedcves;
foreach my $cve (@cves) { if (needs_page($cve)) { $printedcves{$cve} = 1; } }
foreach my $cve (keys %UpdateInfoReader::productsinqa) {
	next if ($CanDBReader::embargoed{$cve});
	next if ($SMASHData::embargoedcves{$cve});
	$printedcves{$cve} = 1;
}

&SMASHData::fetch_last_smash_issues();
#skip this due to time &fetch_modified_smash_issues();
&SMASHData::read_all_cached_issues();

foreach my $cve (keys %SMASHData::pkgstate) {
	next if ($CanDBReader::embargoed{$cve});
	next if ($SMASHData::embargoedcves{$cve});
	$printedcves{$cve} = 1;
}
foreach my $cve (keys %UpdateInfoReader::products) { $printedcves{$cve} = 1; }

foreach my $cve (keys %RancherCVE::status) { $printedcves{$cve} = 1; }

delete $printedcves{'NOT-SECURITY'};

open(SUPPRESSLIST,"$cverepobase/data/suppress");
while (<SUPPRESSLIST>) {
	chomp;
	delete $printedcves{$_};
}
close(SUPPRESSLIST);

my @printedcves = sort cvesortfun keys %printedcves;

my $fastmode = 1;	# FIXME: currently always fastmode.
my $leftcve = "index";
my $rightcve;

my @indexcves = ();

print "generating CVE pages at " . localtime() . "\n";

my $logfile = "$cverepobase/logs/generate-cve-dir-logfile.$$";
open(LOGFILE,">$logfile");

my @allcve = <$cverepobase/cve/CVE*.html>;
my %allcve = ();
foreach my $cve (@allcve) {
	next unless ($cve =~ /(CVE-.*).html/);
	$allcve{$1} = 1;
}
foreach my $cve (@printedcves) {
	next unless (&needs_page($cve));
	if (!defined($allcve{$cve})) {
		print STDERR "going into fast mode as $cve is not yet published.\n";
		$fastmode = 1;
		last;
	}
}

#open(NOCVSS,">/suse/meissner/Export/nocvss");
while (my $cve = shift @printedcves) {
	my %xx;
	my @xx;
	my $str;
	my $released_products = "";
	my %products;
	my %allpackages = ();
	my %overallstate = ();

	# timestamps currently not desired. But leave in for potential later usage.
	# my %timestamps = ();

	print STDERR "$cve\n" if (-t STDERR);

	next unless ($cve =~ /^(CAN|CVE)-(\d*)-\d*$/);

	my $cveyear = $2;

	if (!&needs_page($cve)) {
		print STDERR "unlinked/unreleased CVE id $cve, skipping.\n";
		next;
	}

	if (!$fastmode && (int(rand(($year-$cveyear+1)*4)) == 0)) {	# refresh every 4th cve in current year, every 8th in second last, 12th in third last ... on every run
		invalidate_smash_issue($cve);
	}
	push @indexcves,$cve;
	# print STDERR "$cve...\n";
	$rightcve = $printedcves[0];
	if (!defined($rightcve)) {
		# print STDERR "no right cve?\n";
		$rightcve="index"
	}

	#
	# Main product evaluation loop.
	#
	# We do this before starting to emit HTML as we need the product list
	# in the HTML header.
	#
	my $tablecontent = "";
	my $inqatablecontent = "";

	# first SLE 12 / 11 (not openSUSE)
	if (defined($UpdateInfoReader::products{$cve})) {
		my $products = $UpdateInfoReader::products{$cve};

		#print "update for $cve\n";

		my %prod2pkgpatch = ();

		my %updatestamps = ();	# { timestamp -> { product -> 1 } }
		foreach my $prod (sort keys %{$products}) {
			next if ($prod =~ /openSUSE/);
			next if ($prod =~ /TERADATA/);

			next if (UpdateInfoReader::blacklistedproduct($prod));

			#print "product $prod for $cve\n";
			my $product = $products->{$prod};
			next unless (defined($product->{'packages'}));
			my %packages = %{$product->{'packages'}};
			my %srcpackages = ();
			if (defined($product->{'srcpackages'})) {
				%srcpackages = %{$product->{'srcpackages'}};
			}
			my %patchnames = %{$product->{'patchnames'}};

			# for the later built new smash table...
			foreach my $xprod ($prod,contained($prod)) {
				foreach my $pkg (keys %srcpackages) {
					$overallstate{$xprod}->{$pkg} = "Released";
				}
			}

			# products -> { packages: "packages", patches: "patchnames" }

			foreach my $pkg (sort keys %packages) {
				my $str = "<li><code class=\"cve-released\">$pkg &gt;= $packages{$pkg}</code></li>\n";

				if (	($prod =~ /Live Patching/) && (
						($str =~ /kernel-default-livepatch(-devel|) &gt;= /) ||
						($str =~ /kernel-livepatch-[^ ]* &gt;= 1-/) ||
						($str =~ /kgraft-patch-[^ ]* &gt;= 1-/)
					)
				) {
					# this is the initial livepatch, suppress it
					print "$cve: suppressing initial livepatch in $prod: $str\n";
					next;
				}

				foreach my $xprod ($prod,contained($prod)) {
					$prod2pkgpatch{$xprod}->{"packages"}->{$str}=1;
				}
				$allpackages{$pkg} = 1;
			}
			my @builds = ();
			foreach my $id (sort keys %patchnames) {
				my $patchfinderid = $id;
				# sles11sp1-glibc-4242 -> glibc 4242
				$patchfinderid =~ s/s[^-]*-([^-]*)-([^-]*)$/$1+$2/;

				# skip virtual patch ids, but not GA ones.
				next if (($UpdateInfoReader::patchvirtual{$id}) && ($id !~ /GA/));

				# Timestamps currently not desired.
				#my $ts = $UpdateInfoReader::patchissued{$id};

				# update repos get timestamps minutes or seconds out of each other ... usually we should try a better fuzzy approach
				#$ts -= $ts % 3600;

				#foreach my $xprod ($prod,contained($prod)) {
				#	$updatestamps{$ts}->{$xprod} = 1;
				#}

				# FIXME: needs hotlinks into SCC first.
				#push @builds,$cgi->a({-href=>"http://download.suse.com/patch/finder/#familyId=&productId=&dateRange=&startDate=&endDate=&priority=&distribution=&architecture=&keywords=$patchfinderid"},$id);

				push @builds,$id;
				foreach my $xprod ($prod,contained($prod)) {
					$prod2pkgpatch{$xprod}->{"patches"}->{$id}=1;
				}
			}
		}

		# Timestamps currently not desired.
		#foreach my $stamp (keys %updatestamps) {
		#	$timestamps{$stamp}  = "Published update for " .  join(", ",sort keys %{$updatestamps{$stamp}});
		#}

		my %content2product = ();
		foreach my $prod (sort keys %prod2pkgpatch) {
			my $patchnames = "";
			if ($prod2pkgpatch{$prod}->{"patches"}) {
				$patchnames = "Patchnames: <br/>" . join($cgi->br,sort keys %{$prod2pkgpatch{$prod}->{"patches"}});
			}
			my $restline = $cgi->td([$cgi->ul(sort keys %{$prod2pkgpatch{$prod}->{"packages"}}), $patchnames]);

			my @products = ();
			if (defined($content2product{$restline})) {
				@products = @{$content2product{$restline}};
			}
			push @products,$prod;

			$content2product{$restline} = \@products;
		}
		my @outputlines = ();
		foreach my $restline (sort keys %content2product) {
			my @allproducts = @{$content2product{$restline}};

			push @outputlines, $cgi->Tr({},$cgi->td(join("<br/>\n",@{$content2product{$restline}})),$restline);
		}
		$tablecontent .= join("\n", sort @outputlines). "\n";
	}

	# IN QA updates for SLE (not openSUSE)
	if (defined($UpdateInfoReader::productsinqa{$cve})) {
		my $products = $UpdateInfoReader::productsinqa{$cve};
		#print "update for $cve\n";

		foreach my $prod (sort keys %{$products}) {
			next if ($prod =~ /openSUSE/);
			next if ($prod =~ /TERADATA/);
			next if (UpdateInfoReader::blacklistedproduct($prod));
			#print "product $prod for $cve\n";
			my $product = $products->{$prod};
			next unless (defined($product->{'packages'}));
			my %packages = %{$product->{'packages'}};
			my %srcpackages = ();
			if (defined($product->{'srcpackages'})) {
				%srcpackages = %{$product->{'srcpackages'}};
			}

			# for the later built smash table.
			foreach my $xprod ($prod,contained($prod)) {
				foreach my $pkg (keys %srcpackages) {
					$overallstate{$xprod}->{$pkg} = "In progress";
				}
			}

			my @list = ();
			foreach my $pkg (sort keys %packages) {
				$allpackages{$pkg} = 1;
				push @list,"<li><code class=\"cve-inqa\">$pkg &gt;= $packages{$pkg}</code></li>\n";
			}
			foreach my $xprod ($prod,contained($prod)) {
				$inqatablecontent .= $cgi->Tr({},$cgi->td([$xprod,$cgi->ul(@list),])) . "\n";
			}
		}
	}

	# openSUSE last
	if (defined($UpdateInfoReader::products{$cve})) {
		my %products = %{$UpdateInfoReader::products{$cve}};

		#print "update for $cve\n";

		foreach my $prod (sort keys %products) {
			next unless ($prod =~ /openSUSE/);
			#print "product $prod for $cve\n";
			my $product = $products{$prod};
			next unless (defined($product->{'packages'}));
			my %packages = %{$product->{'packages'}};
			my %srcpackages = ();
			if (defined($product->{'srcpackages'})) {
				%srcpackages = %{$product->{'srcpackages'}};
			}
			my %patchnames = %{$product->{'patchnames'}};

			my $references = "";

			my @list = ();
			foreach my $pkg (sort keys %packages) {
				push @list,"<li><code class=\"cve-released\">$pkg &gt;= $packages{$pkg}</code></li>\n";
			}

			my @builds = sort keys %patchnames; # no patchbuilder refs like for SLES 12
			$references .= "Patchnames: <br/>" . join($cgi->br,@builds);

			$tablecontent .= $cgi->Tr({},$cgi->td([$prod,$cgi->ul(@list),$references])) . "\n";
		}
	}

	my @allpackages	= keys %allpackages;

	if ($tablecontent ne "") {
		# Make it SUSE green.
		$released_products .= "<style>.cve-released {color: #02d35f;background-color: #f2f2f2;}</style>\n";
		$released_products .= $cgi->h3({-class => "fcolor9"}, "List of released packages") . "\n";
		$released_products .= $cgi->start_table({-border => 1}) . "\n";
		$released_products .= $cgi->Tr({},$cgi->th(['Product(s)','Fixed package version(s)','References']));
		$released_products .= "\n";
		$released_products .= $tablecontent;
		$released_products .= "\n";
		$released_products .= $cgi->end_table() . "\n";
		$released_products .= $cgi->p . "\n";
	}
	if ($inqatablecontent ne "") {
		# Restore current previous color. (reddish). Perhaps use bootstrap colors?
		$released_products .= "<style>.cve-inqa {color: #c7254e; background-color: #f9f2f4;}</style>\n";
		$released_products .= $cgi->h3({-class => "fcolor9"}, "List of packages in QA") . "\n";
		$released_products .= $cgi->start_table({-border => 1}) . "\n";
		$released_products .= $cgi->Tr({},$cgi->th(['Product(s)','Package(s)']));
		$released_products .= "\n";
		$released_products .= $inqatablecontent;
		$released_products .= "\n";
		$released_products .= $cgi->end_table() . "\n";
		$released_products .= $cgi->p . "\n";
	}

	############ OUTPUT the stuff ##################################
	open(CVE,">$cvedir/$cve.html.new")||die "$cvedir/$cve.html.new:$!";

	# <meta name="publish_date" content="1999-01-01" />
	my %meta = (
		'Robots' => "INDEX, FOLLOW",
		'keywords' => "$cve, security advisory, suse linux, suse, security, cve"
	);
	if (defined($CanDBReader::lastreleased{$cve})) {
		my $xdate = $CanDBReader::lastreleased{$cve};

		$xdate =~ s/(\d\d\d\d)(\d\d)(\d\d)/$1-$2-$3/;
		$meta{"publish_date"} = $xdate;
	}
	if (%products) {
		$meta{"products"} = join(",",sort keys %products);
	}

	#
	# HTML Header
	#

	print CVE "<!-- meta tags for SEO -->\n";
	print CVE "<div id=\"meta_title\">$cve</div>\n";
	if (%products) {
		print CVE "<div id=\"meta_description\">$meta{\"products\"}</div>\n";	# FIXME ?  probably something else here?
	}
	print CVE "<div id=\"meta_keywords\">$cve, security advisory, suse linux, suse, security, cve</div>\n";

	print CVE "<!-- content for page -->\n";
	print CVE "<div id=\"contenthead\">\n";
		print CVE $cgi->h1("$cve")."\n";
		print CVE $cgi->h2("Common Vulnerabilities and Exposures")."\n";

		print CVE $cgi->a({-href => "$leftcve.html"},"[Previous]") . " ";
		print CVE $cgi->a({-href => "index.html"},"[Index]"). " ";
		print CVE $cgi->a({-href => "$rightcve.html"},"[Next]");

	print CVE "</div>\n";

	print CVE "<div id=\"mainbody\">\n";
	print CVE "<script type=\"text/javascript\">var showRater = false; var showSendToFriend = false;</script>\n";

	#
	#
	# Upstream information ... Mitre links, description, vendor advisories
	#
	#

	print CVE $cgi->h3({-class => "fcolor9"}, "Upstream information")."\n";

	print CVE $cgi->p({-class => "ext-lnk"},
		$cgi->a({-href => "http://cve.mitre.org/cgi-bin/cvename.cgi?name=$cve"},
		"$cve at MITRE"))."\n";
	print CVE $cgi->h4("Description") . "\n";

	# header done ...

	my $desc = get_description($cve);
	if (defined($desc)) {
		#$desc =~ s/&#822[01];/&quot;/g;
		#$desc =~ s/[“”];/&quot;/g;
		#$desc =~ s/’/'/g;
		$desc = $cgi->escapeHTML($desc);
		$desc =~ s/\n/<br\/>/g;
		print CVE $desc . "\n" . $cgi->hr;
	}

	##### SMASH prework
	#
	# fetch the content of state and severity etc. We need to do this before, so we get the vendor advisories.
	#
	&SMASHData::read_smash_issue($cve,0);

	my $state = "Does not affect SUSE products";	# FIXME: would better be Untracked ... but well
	if (defined($SMASHData::state{$cve})) {
		$state = $SMASHData::state{$cve};

		$state =~ s/Not for us/Does not affect SUSE products/;
		$state =~ s/Analyzed/Pending/;	# new name 202211... avoid massive page regeneration

		if ($RancherCVE::status{$cve}) {
			if (	($state eq "Does not affect SUSE products")	||
				($state eq "Resolved")				||
				($state eq "Ignore")
			) {
				$state = "Pending";
			}
		}

		if ($state eq "Ignore") {
			if (!$SMASHData::pkgstate{$cve}) {
				$state = "Does not affect SUSE products";
				# if we have a Rancher or OpenSUSE advisory, mark it as Resolved though.
				if (defined($CanDBReader::advisoryids{$cve})) {
					my %xx = map { $_ => 1 } split(/,/,$CanDBReader::advisoryids{$cve});
					my @xx = sort keys %xx;

					if (scalar(@xx) > -1) {
						foreach my $said (@xx) {
							if (	defined($CanDBReader::advisoryid2url{$said}) &&
								(
									($CanDBReader::advisoryid2url{$said} =~ /rancher/) ||
									($CanDBReader::advisoryid2url{$said} =~ /opensuse/)
								)
							) {
								$state = "Resolved";
							}
						}
					}
				}
			} else {
				my $keepignore = 0;
				my %cvestate = %{$SMASHData::pkgstate{$cve}};
				foreach my $prod (keys %cvestate) {
					foreach my $pkg (keys %{$cvestate{$prod}}) {
						$allpackages{$pkg} = 1;
						if ($cvestate{$prod}->{$pkg} eq "Ignore") {
							$keepignore = 1;
							last;
						}
					}
					last if ($keepignore);
				}
				if (!$keepignore) {
					$state = "Does not affect SUSE products";
				}
			}
		}

		if ($SMASHData::pkgstate{$cve}) {
			my %cvestate = %{$SMASHData::pkgstate{$cve}};
			foreach my $prod (keys %cvestate) {
				foreach my $pkg (keys %{$cvestate{$prod}}) {
					$allpackages{$pkg} = 1;
				}
			}
		}
	}

	my %vendoradvisories = ();

	if (grep(/^curl/,@allpackages)) {
		$vendoradvisories{"curl $cve advisory"} = "https://curl.se/docs/$cve.html";
	}
	foreach my $refname (sort keys %{$SMASHData::vendoradvisories{$cve}}) {
		$vendoradvisories{$refname} = $SMASHData::vendoradvisories{$cve}->{$refname};
	}
	if (%vendoradvisories) {
		print CVE $cgi->h3({-class => "fcolor9"}, "Upstream Security Advisories:") . "\n";
		print CVE $cgi->start_ul(). "\n";
		foreach my $vendor (sort keys %vendoradvisories) {
			# the index entries from smash are unreadable.
			print CVE $cgi->li($cgi->a({-href=>$vendoradvisories{$vendor}},"$vendoradvisories{$vendor}")). "\n";
		}
		print CVE $cgi->end_ul() . "\n";
		print CVE $cgi->p . "\n";
	}

	#
	#
	##### SUSE Part starts here ######
	#
	#

	print CVE $cgi->h3({-class => "fcolor9"}, $cgi->a({-href => "https://www.suse.com/c/cve-pages-self-help-security-issues-suse-linux-enterprise/"}, "SUSE information")) ."\n";
	print CVE $cgi->p("Overall state of this security issue: $state") . "\n";

	if (defined($SMASHData::severity{$cve})) {
		if (($SMASHData::severity{$cve} eq "low") && !(defined($SMASHData::pkgstate{$cve}))) {
			print CVE $cgi->p("This issue is currently not rated by SUSE as it is not affecting the SUSE Enterprise products.") . "\n";
		} else {
			print CVE $cgi->p("This issue is currently rated as having " . $cgi->a({-href=>"https://www.suse.com/support/security/rating.html"},$SMASHData::severity{$cve}) . " severity.") . "\n";
		}
	}

	my $cvss = get_cvss($cve);
	if (defined($cvss)) {
		my %cvss = %{$cvss};
		my @dbs = ();
		my @vals = ();

		if (defined($cvss{'SUSE'}) || defined($cvss{'National Vulnerability Database'}) || grep (/CNA/,keys %cvss)) {
			# table:
			# <empty>       NVD SUSE
			# explanation1  val val
			# explanation2  val val
			# ...
			print CVE $cgi->start_table({-border => 1}).$cgi->caption($cgi->a({href=>"https://nvd.nist.gov/cvss.cfm"},"CVSS v2 Scores")) . "\n";

			# Timestamps currently not desired
			#if (defined($cvss{'SUSE'})) {
			#	$timestamps{$cvss{'SUSE'}->{"created"}} = "Initial SUSE CVSS v2 score assigned.";
			#	$timestamps{$cvss{'SUSE'}->{"modified"}} = "SUSE CVSS v2 score last modified.";
			#}
			#if (defined($cvss{'National Vulnerability Database'})) {
			#	$timestamps{$cvss{'National Vulnerability Database'}->{"created"}} = "Initial NVD CVSS v2 score assigned.";
			#	$timestamps{$cvss{'National Vulnerability Database'}->{"modified"}} = "NVD CVSS v2 score last modified.";
			#}

			@dbs = grep (/SUSE|National|CNA/,sort keys %cvss);
			unshift @dbs,'&nbsp;';
			print CVE $cgi->Tr({},$cgi->th(\@dbs)) . "\n";
			shift @dbs;

			@vals = ('Base Score');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};
				push @vals,$entry{'base_score'};
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Vector');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};
				push @vals,$entry{'base_vector'};
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Access Vector');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'Network' if ($entry{'base_vector'} =~ /AV:N/);
				push @vals, 'Adjacent Network' if ($entry{'base_vector'} =~ /AV:A/);
				push @vals, 'Local' if ($entry{'base_vector'} =~ /AV:L/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Access Complexity');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /AC:H/);
				push @vals, 'Medium' if ($entry{'base_vector'} =~ /AC:M/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /AC:L/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Authentication');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'Multiple' if ($entry{'base_vector'} =~ /Au:M/);
				push @vals, 'Single' if ($entry{'base_vector'} =~ /Au:S/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /Au:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Confidentiality Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'Complete' if ($entry{'base_vector'} =~ /C:C/);
				push @vals, 'Partial' if ($entry{'base_vector'} =~ /C:P/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /C:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Integrity Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'Complete' if ($entry{'base_vector'} =~ /\/I:C/);
				push @vals, 'Partial' if ($entry{'base_vector'} =~ /\/I:P/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/I:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Availability Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'Complete' if ($entry{'base_vector'} =~ /A:C/);
				push @vals, 'Partial' if ($entry{'base_vector'} =~ /A:P/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /A:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			#foreach my $type (sort keys %cvss) {
			#	my %entry = %{$cvss{$type}};
			#	if ($type eq 'National Vulnerability Database') {
			#		print CVE $cgi->p({-class => "ext-lnk"},
			#			   $cgi->a({-href => "http://web.nvd.nist.gov/view/vuln/detail?vulnId=$cve"},
			#			   "NVD CVSS v2 Base Score") .  ": " .
			#			   $entry{'base_score'} ." (".  $entry{'base_vector'} . ")"
			#			  ). "\n";
			#	}
			#	if ($type eq 'SUSE') {
			#		print CVE $cgi->p("SUSE CVSS v2 Base Score: " .
			#			   $entry{'base_score'} ." (".  $entry{'base_vector'} . ")"
			#			  ). "\n";
			#	}
			#	# skip redhat and others.
			#}

			print CVE $cgi->end_table . "\n";
		}
	} else {
		# print NOCVSS "$cve\n";
	}

	# CVSSv3
	$cvss = &SMASHData::get_cvssv3_issue($cve);
	if (defined($cvss)) {
		my %cvss = %{$cvss};
		my @dbs = ();
		my @vals = ();

		if (defined($cvss{'SUSE'}) || defined($cvss{'National Vulnerability Database'}) || grep (/CNA/,keys %cvss)) {
			# table:
			# <empty>       NVD SUSE
			# explanation1  val val
			# explanation2  val val
			# ...
			print CVE $cgi->start_table({-border => 1}).$cgi->caption($cgi->a({href=>"https://nvd.nist.gov/cvss.cfm"},"CVSS v3 Scores")) . "\n";

			@dbs = grep (/SUSE|National|CNA/,sort keys %cvss);
			unshift @dbs,'&nbsp;';
			print CVE $cgi->Tr({},$cgi->th(\@dbs)) . "\n";
			shift @dbs;

			@vals = ('Base Score');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};
				push @vals,$entry{'base_score'};
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			# Timestamps currently not desired
			#if (defined($cvss{'SUSE'})) {
			#	$timestamps{$cvss{'SUSE'}->{"created"}} = "Initial SUSE CVSS v3 score assigned.";
			#	$timestamps{$cvss{'SUSE'}->{"modified"}} = "SUSE CVSS v3 score last modified.";
			#}
			#if (defined($cvss{'National Vulnerability Database'})) {
			#	$timestamps{$cvss{'National Vulnerability Database'}->{"created"}} = "Initial NVD CVSS v3 score assigned.";
			#	$timestamps{$cvss{'National Vulnerability Database'}->{"modified"}} = "NVD CVSS v3 score last modified.";
			#}

			@vals = ('Vector');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};
				push @vals,$entry{'base_vector'};
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Attack Vector');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'Network' if ($entry{'base_vector'} =~ /AV:N/);
				push @vals, 'Adjacent Network' if ($entry{'base_vector'} =~ /AV:A/);
				push @vals, 'Local' if ($entry{'base_vector'} =~ /AV:L/);
				push @vals, 'Physical' if ($entry{'base_vector'} =~ /AV:P/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Attack Complexity');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/AC:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/AC:L/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Privileges Required');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/PR:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/PR:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/PR:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('User Interaction');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'Required' if ($entry{'base_vector'} =~ /\/UI:R/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/UI:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Scope');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'Changed' if ($entry{'base_vector'} =~ /\/S:C/);
				push @vals, 'Unchanged' if ($entry{'base_vector'} =~ /\/S:U/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Confidentiality Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/C:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/C:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/C:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Integrity Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/I:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/I:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/I:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Availability Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/A:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/A:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/A:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";
			@vals = ('CVSSv3 Version');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, $entry{'version'};
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			#foreach my $type (sort keys %cvss) {
			#	my %entry = %{$cvss{$type}};
			#	if ($type eq 'National Vulnerability Database') {
			#		print CVE $cgi->p({-class => "ext-lnk"},
			#			   $cgi->a({-href => "http://web.nvd.nist.gov/view/vuln/detail?vulnId=$cve"},
			#			   "NVD CVSS v2 Base Score") .  ": " .
			#			   $entry{'base_score'} ." (".  $entry{'base_vector'} . ")"
			#			  ). "\n";
			#	}
			#	if ($type eq 'SUSE') {
			#		print CVE $cgi->p("SUSE CVSS v2 Base Score: " .
			#			   $entry{'base_score'} ." (".  $entry{'base_vector'} . ")"
			#			  ). "\n";
			#	}
			#	# skip redhat and others.
			#}

			print CVE $cgi->end_table . "\n";
		}
	} else {
		# print NOCVSS "$cve\n";
	}

	# CVSSv4
	$cvss = &SMASHData::get_cvssv4_issue($cve);
	if (defined($cvss)) {
		my %cvss = %{$cvss};
		my @dbs = ();
		my @vals = ();

		if (defined($cvss{'SUSE'}) || defined($cvss{'National Vulnerability Database'}) || grep (/CNA/,keys %cvss)) {
			# table:
			# <empty>       NVD SUSE
			# explanation1  val val
			# explanation2  val val
			# ...
			print CVE $cgi->start_table({-border => 1}).$cgi->caption($cgi->a({href=>"https://nvd.nist.gov/cvss.cfm"},"CVSS v4 Scores")) . "\n";

			@dbs = grep (/SUSE|National|CNA/,sort keys %cvss);
			unshift @dbs,'&nbsp;';
			print CVE $cgi->Tr({},$cgi->th(\@dbs)) . "\n";
			shift @dbs;

			@vals = ('Base Score');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};
				push @vals,$entry{'base_score'};
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			# Timestamps currently not desired
			#if (defined($cvss{'SUSE'})) {
			#	$timestamps{$cvss{'SUSE'}->{"created"}} = "Initial SUSE CVSS v3 score assigned.";
			#	$timestamps{$cvss{'SUSE'}->{"modified"}} = "SUSE CVSS v3 score last modified.";
			#}
			#if (defined($cvss{'National Vulnerability Database'})) {
			#	$timestamps{$cvss{'National Vulnerability Database'}->{"created"}} = "Initial NVD CVSS v3 score assigned.";
			#	$timestamps{$cvss{'National Vulnerability Database'}->{"modified"}} = "NVD CVSS v3 score last modified.";
			#}

			@vals = ('Vector');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};
				push @vals,$entry{'base_vector'};
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Attack Vector');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'Network' if ($entry{'base_vector'} =~ /AV:N/);
				push @vals, 'Adjacent Network' if ($entry{'base_vector'} =~ /AV:A/);
				push @vals, 'Local' if ($entry{'base_vector'} =~ /AV:L/);
				push @vals, 'Physical' if ($entry{'base_vector'} =~ /AV:P/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Attack Complexity');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/AC:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/AC:L/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";
			@vals = ('Attack Requirements');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'None' if ($entry{'base_vector'} =~ /\/AT:N/);
				push @vals, 'Present' if ($entry{'base_vector'} =~ /\/AT:P/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Privileges Required');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/PR:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/PR:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/PR:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('User Interaction');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'None' if ($entry{'base_vector'} =~ /\/UI:N/);
				push @vals, 'Passive' if ($entry{'base_vector'} =~ /\/UI:P/);
				push @vals, 'Active' if ($entry{'base_vector'} =~ /\/UI:A/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Vulnerable System Confidentiality Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/VC:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/VC:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/VC:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Vulnerable System Integrity Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/VI:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/VI:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/VI:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Vulnerable System Availability Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/VA:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/VA:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/VA:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";
			@vals = ('Subsequent System Confidentiality Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/SC:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/SC:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/SC:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Subsequent System Integrity Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/SI:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/SI:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/SI:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('Subsequent System Availability Impact');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, 'High' if ($entry{'base_vector'} =~ /\/SA:H/);
				push @vals, 'Low' if ($entry{'base_vector'} =~ /\/SA:L/);
				push @vals, 'None' if ($entry{'base_vector'} =~ /\/SA:N/);
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			@vals = ('CVSSv4 Version');
			foreach my $db (@dbs) {
				my %entry = %{$cvss{$db}};

				push @vals, sprintf("%.1f",$entry{'version'});
			}
			print CVE $cgi->Tr({},$cgi->td(\@vals)) . "\n";

			#foreach my $type (sort keys %cvss) {
			#	my %entry = %{$cvss{$type}};
			#	if ($type eq 'National Vulnerability Database') {
			#		print CVE $cgi->p({-class => "ext-lnk"},
			#			   $cgi->a({-href => "http://web.nvd.nist.gov/view/vuln/detail?vulnId=$cve"},
			#			   "NVD CVSS v2 Base Score") .  ": " .
			#			   $entry{'base_score'} ." (".  $entry{'base_vector'} . ")"
			#			  ). "\n";
			#	}
			#	if ($type eq 'SUSE') {
			#		print CVE $cgi->p("SUSE CVSS v2 Base Score: " .
			#			   $entry{'base_score'} ." (".  $entry{'base_vector'} . ")"
			#			  ). "\n";
			#	}
			#	# skip redhat and others.
			#}

			print CVE $cgi->end_table . "\n";
		}
	} else {
		# print NOCVSS "$cve\n";
	}

	$leftcve = $cve;

	if (defined($CanDBReader::note{$cve})) {
		print CVE $cgi->h4("Note from the SUSE Security Team") . "\n";
		print CVE $CanDBReader::note{$cve};
		print CVE $cgi->p . "\n";
	}

	foreach my $pnote (sort keys %CanDBReader::packagenotes) {
		if (grep (/^$pnote/,@allpackages)) {
			print CVE $cgi->h4("Note from the SUSE Security Team on the $pnote package") . "\n";
			print CVE $CanDBReader::packagenotes{$pnote};
			print CVE $cgi->p . "\n";
		}
	}

	#
	# Timestamps currently not desired
	#
	#if (defined($CanDBReader::firstdate{$cve})) {
	#	my ($year,$month,$day) = ($CanDBReader::firstdate{$cve} =~ /(\d\d\d\d)(\d\d)(\d\d)/);
	#	my $dt = DateTime->new( year => $year, month => $month, day => $day);
	#
	#	$timestamps{$dt->epoch} = "SUSE bugzilla entry first opened.";
	#}
	#if (defined($SMASHData::timestamps{$cve}->{analysis})) {
	#	$timestamps{$SMASHData::timestamps{$cve}->{analysis}} = "CVE entered analysis state.";
	#}
	#if (defined($SMASHData::timestamps{$cve}->{pending})) {
	#	$timestamps{$SMASHData::timestamps{$cve}->{pending}} = "CVE finished analysis state, declared as affected.";
	#}
	#if (defined($SMASHData::timestamps{$cve}->{resolved})) {
	#	$timestamps{$SMASHData::timestamps{$cve}->{resolved}} = "CVE declared as resolved.";
	#}
	#if (defined($SMASHData::timestamps{$cve}->{running})) {
	#	$timestamps{$SMASHData::timestamps{$cve}->{running}} = "First update for CVE submitted.";
	#}
	#if (defined($SMASHData::timestamps{$cve}->{ignored})) {
	#	$timestamps{$SMASHData::timestamps{$cve}->{ignored}} = "CVE finished analysis state, declared as not affected.";
	#}
	# this is triggering too often.
	# if (defined($SMASHData::timestamps{$cve}->{modified})) {
	# 	$timestamps{$SMASHData::timestamps{$cve}->{modified}} = "CVE meta data or tracking last modified.";
	# }

	if (defined($CanDBReader::bugzillas{$cve})) {
		%xx = map { $_ => 1 } split(/,/,$CanDBReader::bugzillas{$cve});
		@xx = sort keys %xx;

		if ($#xx > -1) {
			$str="";
			foreach my $bug (@xx) {
				my $state;
				my $resolution;
				if (defined($CanDBReader::bugzilla_state{$bug})) {
					$state = $CanDBReader::bugzilla_state{$bug};
					$resolution = $CanDBReader::bugzilla_resolution{$bug};

					if (defined($CanDBReader::bugzilla_resolution{$bug}) && ($state eq "CLOSED" || $state eq "VERIFIED" || $state eq "RESOLVED")) {
						$state = " [$state / $resolution]";
					} else {
						$state = " [$state]";
					}
				} else {
					$state = " [NEW]";
					print STDERR "no state for bug $bug, assuming NEW.\n" if (-t STDERR);
				}

				$bug = $cgi->a({-href=>"https://bugzilla.suse.com/show_bug.cgi?id=$bug"},$bug) . $state;
			}
			if ($#xx>=1) {
				print CVE "SUSE Bugzilla entries: ";
			} else {
				print CVE "SUSE Bugzilla entry: ";
			}
			print CVE join (", ",@xx) . "\n";
		} else {
			print CVE "No SUSE Bugzilla entries cross referenced.\n";
		}
	} else {
		print CVE "No SUSE Bugzilla entries cross referenced.\n";
	}
	print CVE $cgi->p . "\n";

	if (defined($CanDBReader::advisoryids{$cve})) {
		print CVE $cgi->h3({-class => "fcolor9"}, "SUSE Security Advisories:") . "\n";
		%xx = map { $_ => 1 } split(/,/,$CanDBReader::advisoryids{$cve});
		@xx = sort keys %xx;

		print CVE $cgi->start_ul();
		if (scalar(@xx) > -1) {
			foreach my $said (@xx) {
				if (defined($CanDBReader::advisoryid2url{$said}) && defined($CanDBReader::advisoryid2date{$said})) {
					$said = $cgi->li($cgi->a({-href=>$CanDBReader::advisoryid2url{$said}},$said).", published ". $CanDBReader::advisoryid2date{$said});
				} else {
					print STDERR "SA ID: $said not linked to url?\n" if (-t STDERR);
				}
			}
		}
		print CVE join("\n",@xx) . $cgi->end_ul();
	} else {
		print CVE "No SUSE Security Announcements cross referenced.\n";
	}
	print CVE $cgi->p . "\n";

	# was generated earlier ...

	if (!$todaypublic{$cve}) {		# if we disclose it today, do not yet print the package list.
		print CVE $released_products;

		if ($PInt::cve2images{$cve}) {
			print CVE $cgi->hr;
			print CVE $cgi->h3({-class => "fcolor9"}, "First public cloud image revisions this CVE is fixed in:") . "\n";

			my @list;
			foreach my $tag (sort keys %{$PInt::cve2images{$cve}}) {
				push @list,$cgi->li($cgi->a({-href=>"https://publiccloudimagechangeinfo.suse.com/$tag"},$tag)). "\n";
			}
			print CVE $cgi->ul(@list);
			print CVE "\n";
		}

		# SMASH Status Info
		if (defined($SMASHData::codestreampkgstate{$cve})) {
			my %state = %{$SMASHData::codestreampkgstate{$cve}};

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

				foreach my $pkg (sort keys %pkg) {
					next unless (defined($UpdateInfoReader::codestreampkg2container{"$codestream.$pkg"}));
					foreach my $container (keys %{$UpdateInfoReader::codestreampkg2container{"$codestream.$pkg"}}) {

						next if ($container =~ /^SLE/ && ($container !~ /CHOST/));	# skip again all images, except CHOST.

						# "Codestream released" does not imply "container released"... we pick this up
						# from the last container info we loaded.
						$SMASHData::pkgstate{$cve}->{$container}->{$pkg} = $pkg{$pkg};
						if (($pkg{$pkg} eq "Released") && (!defined($UpdateInfoReader::containerpkgfixedcve{$container}->{$pkg}->{$cve}))) {
							$SMASHData::pkgstate{$cve}->{$container}->{$pkg} = "In progress";
						}
						# for new method.
						$overallstate{$container}->{$pkg} = $SMASHData::pkgstate{$cve}->{$container}->{$pkg};
					}
				}
			}
		}
		# New SMASH Status INFO:
		# TODO: integrate back the LTSS release state.

		# We fill in more of the "overallstate" for this CVE
		# The Released and In Progress relations for products have been filled into the overallstate already.
		if (defined($SMASHData::pkgstate{$cve})) {
			my %state = %{$SMASHData::pkgstate{$cve}};

			# duplicate for all base products of modules.
			foreach my $prod (sort keys %state) {
				next if (UpdateInfoReader::blacklistedproduct($prod));
				next if ($prod =~ /TERADATA/i);

				my %prodstate = %{$state{$prod}};
				my @contained = contained($prod);

				push @contained,$prod;
				foreach my $contained (sort @contained) {
					if (!defined($overallstate{$contained})) {
						$overallstate{$contained} = {};
					}
					foreach my $pkg (sort keys %prodstate) {
						# we transfer the SMASH pkg state to the overallstate state. But only if we did not
						# it before from released or in progress updates.
						if (!defined($overallstate{$contained}->{$pkg})) {
							# disable for now, as it might not show the right thing.
							# if ($prodstate{$pkg} eq "Unsupported") {
							#	$overallstate{$contained}->{$pkg} = "Affected";
							#} else {

							$overallstate{$contained}->{$pkg} = $prodstate{$pkg};

							#}
						}
					}
				}
			}
		}
		if (defined($SMASHData::codestreampkgstate{$cve})) {
			my %content2product = ();
			my %state = %{$SMASHData::codestreampkgstate{$cve}};

			foreach my $codestream (sort keys %state) {
				next unless (defined($UpdateInfoReader::codestream2product2src2binary{$codestream}));
				my %codestreamstate = %{$state{$codestream}};

				my %productmap = %{$UpdateInfoReader::codestream2product2src2binary{$codestream}};

				foreach my $product (sort keys %productmap) {
					next if ($product =~ /TERADATA/i);
					my %codestreammap = %{$productmap{$product}};

					foreach my $pkg (sort keys %codestreamstate) {
						next unless ($codestreammap{$pkg});

						my $inprogress = 0;

						if ($pkg =~ /^kernel-(source|default|rt|azure)/) {
							my $xcodestream = $codestream;

							$xcodestream =~ s/^SUSE:SLE-([^:]*):.*Update/$1/;
							if (defined($CanDBReader::kernelcvedates{$xcodestream}->{$cve})) {
								$inprogress = 1;
							}
						}

						# print STDERR "relation $product -> $codestream -> $pkg -> " . $codestreamstate{$pkg} . "\n" if -t STDERR;

						# Affected / Not affected / Already fixed directly translate
						# Released / In Progress means Affected for the non-listed LTSS products.
						my $prodstate = $codestreamstate{$pkg};
						$prodstate = "Affected" if ($prodstate eq "Released");	# Not released for this product.
						$prodstate = "Affected" if ($prodstate eq "In progress");# Not released for this product.

						foreach my $contained (contained($product),$product) {
							# special logic to integrate the kernel affectedness. Do this only for general support products.
							if (	$inprogress &&
								(	defined($overallstate{$contained}) &&
									defined($overallstate{$contained}->{$pkg}) &&
									($overallstate{$contained}->{$pkg} eq "Affected")
								) &&
								($Products::product2state{$contained} eq "generalsupport") # otherwise too much confusion if LTSS never gets the fix.
							) {
								$overallstate{$contained}->{$pkg} = "In progress";
								next;
							}
							# got the state from the released or inqa products
							next if (defined($overallstate{$contained}->{$pkg}));

							$overallstate{$contained}->{$pkg} = $prodstate;
						}
					}
				}
			}

			if (%overallstate || $RancherCVE::status{$cve}) {# might happen if we have "hidden products" like Teradata
				print CVE $cgi->hr;
				# print CVE "<style>.cve-status { border-bottom: 1px solid black; background-color: initial }</style>\n";
				print CVE "<style>.cve-status { background-color: initial }</style>\n";
				print CVE $cgi->h3({-class => "fcolor9"}, "Status of this issue by product and package") . "\n";
				print CVE $cgi->p("Please note that this evaluation state might be work in progress, incomplete or outdated. Also information for service packs in the LTSS phase is only included for issues meeting the LTSS criteria. If in doubt, feel free to contact us for clarification. The updates are grouped by state of their lifecycle. SUSE product lifecycles are documented <a href=\"https://www.suse.com/lifecycle/\">on the lifecycle page</a>.\n");
				print CVE $cgi->start_table({-border => 1}) . "\n";
				print CVE $cgi->Tr({},$cgi->th(['Product(s)','Source package','State']));
				print CVE "\n";
			}

			my %lcstates = (
				"generalsupport" =>	"Products under general support and receiving all security fixes.",
				"ltss" => 		"Products under Long Term Service Pack support and receiving important and critical security fixes.",
				"eol" => 		"Products past their end of life and not receiving proactive updates anymore.",
				"unknown" => 		"Products at an unknown state of their lifecycle.",
			);

			foreach my $lcstate ("generalsupport","ltss","eol","unknown") {
				my @lines = ();
				foreach my $prod (sort keys %overallstate) {
					if (	(!defined($Products::product2state{$prod}) && ($lcstate eq "unknown")) ||
						(defined($Products::product2state{$prod}) && ($Products::product2state{$prod} eq $lcstate))
					) {
						my %prodstate = %{$overallstate{$prod}};

						foreach my $pkg (sort keys %prodstate) {
							my $bootstrapclass;
							if (defined($bootstrapmap{$prodstate{$pkg}})) {
								$bootstrapclass = $bootstrapmap{$prodstate{$pkg}};
							} else {
								die "unknown state $prodstate{$pkg}\n";
								$bootstrapclass = "alert-info";
							}
							if (($prod =~ /^[a-z]/) && ($prod !~ /^openSUSE Leap/)) { # testing merged cells
								$content2product{$cgi->td($pkg).$cgi->td({-class => "$bootstrapclass cve-status"},$prodstate{$pkg})}->{$prod} = 1;
							} else {
								push @lines,$cgi->TR($cgi->td($prod),$cgi->td($pkg),$cgi->td({-class => "$bootstrapclass cve-status"},$prodstate{$pkg})) . "\n";
							}
						}
					}
				}
				if (@lines) {
					print CVE $cgi->Tr($cgi->th({-colspan => "3"},$lcstates{$lcstate})) . "\n";
					print CVE @lines;
				}
			}
			# New merged cells emitter ... containers only for now... they are outside.
			if (%content2product) {
				print CVE $cgi->Tr($cgi->th({-colspan => "3"},"Container Status")) . "\n";
				foreach my $content (sort keys %content2product) {
					print CVE $cgi->TR($cgi->td(join("<br/>\n",sort keys $content2product{$content})), $content). "\n";
				}
			}

			# Rancher CVE container injection.
			my %ranchersections = ();
			if ($RancherCVE::status{$cve}) {
				my %rancherprodstatus = %{$RancherCVE::status{$cve}};

				foreach my $rancherprod (sort keys %rancherprodstatus) {
					my %ranchercontainerstatus = %{$rancherprodstatus{$rancherprod}};
					foreach my $ranchercontainer (sort keys %ranchercontainerstatus) {
						my %rancherstatus = %{$ranchercontainerstatus{$ranchercontainer}};
						foreach my $pkg (sort keys %rancherstatus) {
							my $bootstrapclass;
							if (defined($bootstrapmap{$rancherstatus{$pkg}})) {
								$bootstrapclass = $bootstrapmap{$rancherstatus{$pkg}};
							} else {
								warn "unknown state $rancherstatus{$pkg}\n";
								$bootstrapclass = "alert-info";
							}
							my $status = $rancherstatus{$pkg};

							$pkg =~ s/,/<br>/g;

							$ranchersections{$rancherprod}->{$cgi->td($pkg).$cgi->td({-class => "$bootstrapclass cve-status"},$status)}->{$ranchercontainer} = 1;
						}
					}
				}
			}
			foreach my $section (sort keys %ranchersections) {
				my %content2product = %{$ranchersections{$section}};
				print CVE $cgi->Tr($cgi->th({-colspan => "3"},$section)) . "\n";
				foreach my $content (sort keys %content2product) {
					print CVE $cgi->TR($cgi->td(join("<br/>\n",sort keys $content2product{$content})), $content). "\n";
				}
			}

			print CVE $cgi->end_table() . "\n";
			print CVE $cgi->p . "\n";
		} else {
			print STDERR "$cve: no codestream state\n";
		}
	}

	# read the old page, parse out the created and last modified times for use in the new page.

	my $oldcvepage;

	my $created;
	my $lastmodified;
	if (open(OLDCVE,"<$cvedir/$cve.html")) {
		$oldcvepage = join("",<OLDCVE>);
		close(OLDCVE);

		if ($oldcvepage =~ /CVE page created: ([^<]*)</) {
			$created = $1;
		}
		if ($oldcvepage =~ /CVE page last modified: ([^<]*)</) {
			$lastmodified = $1;
		}
	}

	if (!defined($created)) {	# fallback to SMASH creation time.
		if (defined($SMASHData::timestamps{$cve}->{"created"})) {
			$created = localtime($SMASHData::timestamps{$cve}->{"created"});
		} else {
			$created = localtime;
		}
	}
	if (!defined($lastmodified)) {
		$lastmodified = localtime;
	}

	# new page timestamps ... use the old ones, we will replace last modified if anything changed.
	print CVE $cgi->hr;
	print CVE $cgi->h3({-class => "fcolor9"}, "SUSE Timeline for this CVE") . "\n";

	print CVE "CVE page created: $created<br/>\n";			# if you change it here, change it above and below too!
	print CVE "CVE page last modified: $lastmodified<br/>\n";	# if you change it here, change it above and below below too!

	# first try at timestamps was too detailed for us currently.
	#	print CVE $cgi->start_table({-border => 1}) . "\n";
	#	print CVE $cgi->Tr({},$cgi->th(['Timestamp','Change'])) . "\n";
	#	foreach my $timestamp (sort keys %timestamps) {
	#		my $ctime = localtime($timestamp);
	#		print CVE $cgi->TR($cgi->td($ctime),$cgi->td($timestamps{$timestamp})). "\n";
	#	}
	#	print CVE $cgi->end_table() . "\n";

	print CVE "</div>\n";

	close(CVE) || die "write out of $cvedir/$cve.html failed: $!";

	open(CVE,"<$cvedir/$cve.html.new")||die "could not open: $cvedir/$cve.html.new";
	my $newcvepage = join("",<CVE>);
	close(CVE);


	if ($oldcvepage ne $newcvepage) {
		# if the page changed (minus changelog entries), create a new last modified entry and rewrite it.
		$lastmodified = localtime;
		$newcvepage =~ s/CVE page last modified: ([^<]*)</CVE page last modified: $lastmodified</;
		open(CVE,">$cvedir/$cve.html.new")||die;
		print CVE $newcvepage;
		close(CVE);
	}

	if ($oldcvepage ne $newcvepage) {
		if (system("diff -uN $cvedir/$cve.html $cvedir/$cve.html.new")) {

			push @changedpages, "$cve.html";

			if ($refreshcount > 0) {
				&SMASHData::invalidate_smash_issue($cve);
				$refreshcount--;
			}
			if (-f "$cvedir/$cve.html") {
				print STDERR "CHANGED: $cve\n" if (-t STDERR);
			} else {
				print STDERR "NEW: $cve\n" if (-t STDERR);
			}

			# Protocol the change done
			if ($SMASHData::state{$cve} ne "Not for us") {
				open(DIFF,"w3m -T text/html -no-graph -dump $cvedir/$cve.html > before;w3m -T text/html -no-graph -dump $cvedir/$cve.html.new > after;diff -wu before after|");
				my @difflines = <DIFF>;
				close(DIFF);
				my @newdifflines = ();
				foreach my $diffline (@difflines) {
					if ($diffline =~ /^[+-]\|------/) {	# table seperator
						next;
					}
					push @newdifflines,$diffline;
				}
				@difflines = @newdifflines;

				# fixme ... massage diff ...

				if (-f "$cvedir/$cve.html") {
					if (@difflines) {
						print LOGFILE "CHANGED: $cve https://www.suse.com/security/cve/$cve.html\n";
					} # else we might just changed Next Prev of neighbouring pages of a NEW page
				} else {
					print LOGFILE "NEW: $cve https://www.suse.com/security/cve/$cve.html\n";
				}

				print LOGFILE "\tState: "  . $SMASHData::state{$cve}    . "\n" if (defined($SMASHData::state{$cve}));
				print LOGFILE "\tRating: " . $SMASHData::severity{$cve} . "\n" if (defined($SMASHData::severity{$cve}));
				print LOGFILE "\tNote: "   . $CanDBReader::note{$cve}   . "\n" if (defined($CanDBReader::note{$cve}));
				print LOGFILE "\n";
				print LOGFILE @difflines;

			}
			rename("$cvedir/$cve.html.new","$cvedir/$cve.html")||die "rename $cvedir/$cve.html.new $cvedir/$cve.html: $!";
			system("git -C $cvedir/ add $cve.html");

		} else {
			print STDERR "$cve - string different, but no diff?\n";
			# not needed
			unlink("$cvedir/$cve.html.new");
		}
	} else {
		unlink("$cvedir/$cve.html.new");
	}
}
#close(NOCVSS);
close(LOGFILE);
if (-s "$logfile" <= 100000 && -s "$logfile" > 1) {	# do not mail mega diffs, nor empty ones.
	system("mail -s 'CVE Page change' meissner\@suse.de < $logfile");
}
unlink($logfile);

open(CVEINDEX,">$cvedir/index.html");

print CVEINDEX "<!-- meta tags for SEO -->\n";
print CVEINDEX "<div id=\"meta_title\">SUSE CVE Database</div>\n";
print CVEINDEX "<div id=\"meta_description\"></div>\n";
print CVEINDEX "<div id=\"meta_keywords\">security advisory, cve, suse, suse linux, opensuse</div>\n";


print CVEINDEX "<!-- banner area -->\n";
print CVEINDEX "<div id=\"contenthead\">\n";
print CVEINDEX $cgi->h1("SUSE CVE Database") . "\n";
print CVEINDEX $cgi->h2("Common Vulnerabilities and Exposures")."\n";
print CVEINDEX "</div>\n";

print CVEINDEX "<!-- content for page -->\n";
print CVEINDEX "<div id=\"mainbody\">\n";
print CVEINDEX "<script type=\"text/javascript\">var showRater = false; var showSendToFriend = false;</script>\n";

print CVEINDEX "This page lists all CVEs that relate to software shipped by SUSE, including rating, affectedness, QA and update release status and other information.\n";
print CVEINDEX "CVE entries that are not related to software shipped by SUSE are not listed here.\n";

my %year = ();
foreach my $cve (@indexcves) {
	$cve =~ /C..-(\d\d\d\d)-.*/;
	if (!defined($year{$1})) {
		$year{$1} = 1;
	} else {
		$year{$1} = $year{$1}+1;
	}

}
foreach my $year (reverse sort keys %year) {
	print CVEINDEX $cgi->h3({-class => "fcolor9"}, "$year (" . $year{$year} . ")")."\n";
	for my $cve (sort cvesortfun grep (/^C..-$year-/,@indexcves)) {
		print CVEINDEX $cgi->a({-href => "$cve.html"},"$cve") . "\n";
	}
	print CVEINDEX $cgi->hr;
}

print CVEINDEX "</div>\n";

close(CVEINDEX);
push @changedpages, "index.html";

my $tag = sprintf("deploy-0.22.%d%02d%02d%02d%02d%02d",$year,$mon,$mday,$hour,$min,$sec);
print STDERR "tag would be $tag\n";

# git add have happened before except for index.html.

system("cd $cvedir ; git add index.html ; git commit -m \"cve page update by script\" ; git tag $tag ;  git pull --rebase; git push ; git push --tags");

print "Finished at " . localtime . "\n";
print "SUCCESS\n";
