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

# if you change anything, also notify SAP: vas@sap.com

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);

my $cvrfdir="/mounts/mirror/SuSE/ftp.suse.com/pub/projects/security/cvrf-cve/";

use POSIX qw/strftime setlocale LC_TIME/;

require CanDBReader;
require CVEListReader;
require CPE;
require UpdateInfoReader;
UpdateInfoReader->import_product_updates();
UpdateInfoReader->import_images();
require SMASHData;
&SMASHData::read_all_cached_issues();

require PInt;
require ModuleContained;

use IO::File;
use XML::Writer;

my %chostnames = (
	'amazon.*15-sp1'	=> "SLES 15 SP1 CHOST Images for Amazon EC2",
	'amazon.*15-sp2'	=> "SLES 15 SP2 CHOST Images for Amazon EC2",
	'amazon.*15-sp3'	=> "SLES 15 SP3 CHOST Images for Amazon EC2",
	'amazon.*15-sp4'	=> "SLES 15 SP4 CHOST Images for Amazon EC2",
	'google.*15-sp1'	=> "SLES 15 SP1 CHOST Images for Google",
	'google.*15-sp2'	=> "SLES 15 SP2 CHOST Images for Google",
	'google.*15-sp3'	=> "SLES 15 SP3 CHOST Images for Google",
	'google.*15-sp4'	=> "SLES 15 SP4 CHOST Images for Google",
	'microsoft.*15-sp1'	=> "SLES 15 SP1 CHOST Images for Microsoft Azure",
	'microsoft.*15-sp2'	=> "SLES 15 SP2 CHOST Images for Microsoft Azure",
	'microsoft.*15-sp3'	=> "SLES 15 SP3 CHOST Images for Microsoft Azure",
	'microsoft.*15-sp4'	=> "SLES 15 SP4 CHOST Images for Microsoft Azure",
);

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

sub
filter_score($) {
        my ($score) = @_;

        $score =~ s/^(\d*\.\d)\d*$/$1/;
        return $score;
}

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($CanDBReader::embargoed{$cve}) || defined($SMASHData::embargoedcves{$cve})) {
		print STDERR "$cve is embargoed\n" if (-t STDERR);
		return 0;
	}

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

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

delete $printedcves{'NOT-SECURITY'};

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

my @printedcves = sort cvesortfun keys %printedcves;

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

# @printedcves = ('CVE-2021-33200');

while (my $cve = pop @printedcves) {
	my %xx;
	my @xx;
	my $str;
	my $released_products = "";
	my %products;

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

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

	if (!&needs_page($cve)) {
		print STDERR "unlinked/unreleased CVE id $cve, skipping.\n";
		next;
	}
	# fetch the content of state and severity etc.
	&SMASHData::read_smash_issue($cve,0);

	############ OUTPUT the stuff ##################################

	my $fn = "$cvrfdir/cvrf-$cve.xml";

	my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat $fn;
	my $cvemtime;
	($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$cvemtime,$ctime,$blksize,$blocks) = stat "$cverepobase/cve/$cve.html";

	if ($cvemtime < $mtime) {
		print "skipping $cve as CVE page is older than CVRF-CVE file.\n";
		next;
	}

	my $output = IO::File->new(">$fn.new");
	my $writer = XML::Writer->new(OUTPUT => $output, NEWLINES => 1, ENCODING => "utf-8");
	$writer->xmlDecl("UTF-8");
	$writer->startTag("cvrfdoc",
		"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
		"xmlns:cpe" => "http://cpe.mitre.org/language/2.0",
		"xmlns:cvrf" => "http://docs.oasis-open.org/csaf/ns/csaf-cvrf/v1.2/cvrf",
		"xmlns:cvrf-common" => "http://docs.oasis-open.org/csaf/ns/csaf-cvrf/v1.2/common",
		"xmlns:cvssv2" => "http://scap.nist.gov/schema/cvss-v2/1.0",
		"xmlns:cvssv3" => "https://www.first.org/cvss/cvss-v3.0.xsd",
		"xmlns:dc" => "http://purl.org/dc/elements/1.1/",
		"xmlns:ns0" => "http://purl.org/dc/elements/1.1/",
		"xmlns:prod" => "http://docs.oasis-open.org/csaf/ns/csaf-cvrf/v1.2/prod",
		"xmlns:scap-core" => "http://scap.nist.gov/schema/scap-core/1.0",
		"xmlns:sch" => "http://purl.oclc.org/dsdl/schematron",
		"xmlns:vuln" => "http://docs.oasis-open.org/csaf/ns/csaf-cvrf/v1.2/vuln",
		"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
		"xmlns" => "http://docs.oasis-open.org/csaf/ns/csaf-cvrf/v1.2/cvrf",
	);
	$writer->dataElement("DocumentTitle", $cve, "xml:lang" => "en");
	$writer->dataElement("DocumentType", "SUSE CVE");

	$writer->startTag("DocumentPublisher", "Type" => "Vendor");
		$writer->dataElement("ContactDetails", "security\@suse.de");
		$writer->dataElement("IssuingAuthority", "SUSE Security Team");
	$writer->endTag("DocumentPublisher");

	$writer->startTag("DocumentTracking");

		$writer->startTag("Identification");
			$writer->dataElement("ID", "SUSE $cve");
		$writer->endTag("Identification");

		$writer->dataElement("Status", "Interim");	# FIXME
		$writer->dataElement("Version", "1");		# FIXME

		my $dt = DateTime->now;

		# START OF this section might be adjusted later on in a change-detected logic!
		$writer->startTag("RevisionHistory");
			$writer->startTag("Revision");
				$writer->dataElement("Number", "1");
				$writer->dataElement("Date", $dt->iso8601()."Z");
				$writer->dataElement("Description", "current");
			$writer->endTag("Revision");
		$writer->endTag("RevisionHistory");

		$writer->dataElement("InitialReleaseDate",$dt->iso8601()."Z");
		$writer->dataElement("CurrentReleaseDate",$dt->iso8601()."Z");

		# END OF this section might be adjusted later on in a change-detected logic!

		$writer->startTag("Generator");
			$writer->dataElement("Engine","cve-database/bin/generate-cvrf-cve.pl");
			$writer->dataElement("Date","2020-12-27T01:00:00Z");
		$writer->endTag("Generator");

	$writer->endTag("DocumentTracking");

	$writer->startTag("DocumentNotes");
		$writer->dataElement(
			"Note",
			$cve,
			"Title" => "CVE", "Type" => "Summary", "Ordinal" => "1", "xml:lang" => "en"
		);
		my $desc = get_description($cve);
		if (defined($desc)) {
			$writer->dataElement(
				"Note",
				$desc,
				"Title" => "Mitre CVE Description", "Type" => "Description", "Ordinal" => "2", "xml:lang" => "en"
			);
		}
		if ($CANDBReader::note{$cve}) {
			$writer->dataElement(
				"Note",
				$CANDBReader::note{$cve},
				"Title" => "Note from SUSE Security Team", "Type" => "Details", "Ordinal" => "3", "xml:lang" => "en"
			);
		}
		$writer->dataElement(
			"Note",
			"The CVRF data is provided by SUSE under the Creative Commons License 4.0 with Attribution (CC-BY-4.0).",
			"Title" => "Terms of Use", "Type" => "Legal Disclaimer", "Ordinal" => "4", "xml:lang" => "en"
		);
	$writer->endTag("DocumentNotes");

	$writer->startTag("DocumentReferences");
		if (defined($CanDBReader::advisoryids{$cve})) {
			my %xx = map { $_ => 1 } split(/,/,$CanDBReader::advisoryids{$cve});
			foreach my $said (sort keys %xx) {
				if (defined($CanDBReader::advisoryid2url{$said})) {
					$writer->startTag("Reference", "Type" => "Self");
						$writer->dataElement("URL", $CanDBReader::advisoryid2url{$said});
						$writer->dataElement("Description", "E-Mail link for $said");
					$writer->endTag("Reference");
				}
			}
		}
		$writer->startTag("Reference", "Type" => "Self");
			$writer->dataElement("URL", "https://www.suse.com/support/security/rating/");
			$writer->dataElement("Description", "SUSE Security Ratings");
		$writer->endTag("Reference");
	$writer->endTag("DocumentReferences");

	$writer->startTag("ProductTree", "xmlns" => "http://docs.oasis-open.org/csaf/ns/csaf-cvrf/v1.2/prod");


		my %allproducts = ();
		my %packages = ();

		if (defined($UpdateInfoReader::products{$cve})) {
			my %xproducts = %{$UpdateInfoReader::products{$cve}};

			foreach my $product (keys %xproducts) {
				next if (UpdateInfoReader::blacklistedproduct($product));

				if (!defined($xproducts{$product}->{'packages'})) {
					print STDERR "$cve: no packages for $product, but entry?\n";
					next;
				}
				my %pkgs = %{$xproducts{$product}->{'packages'}};

				$allproducts{$product} = 1;
				foreach my $pkg (keys %pkgs) {
					next if ($pkg =~ /-debuginfo/);
					next if ($pkg =~ /-debugsource/);
					$packages{"$pkg-".$pkgs{$pkg}} = 1;
				}
			}
		}
		if (defined($UpdateInfoReader::productsinqa{$cve})) {
			my %xproducts = %{$UpdateInfoReader::productsinqa{$cve}};

			foreach my $product (keys %xproducts) {
				next if (UpdateInfoReader::blacklistedproduct($product));
				next unless (defined($xproducts{$product}->{'packages'}));

				my %pkgs = %{$xproducts{$product}->{'packages'}};
				$allproducts{$product} = 1;
				foreach my $pkg (keys %pkgs) {
					next if ($pkg =~ /-debuginfo/);
					next if ($pkg =~ /-debugsource/);
					$packages{"$pkg-".$pkgs{$pkg}} = 1;
				}
			}
		}

		# calculate all smash products we have...
		my %smashprods = ();
		if (defined ($SMASHData::pkgstate{$cve})) {
			%smashprods= %{$SMASHData::pkgstate{$cve}};
		}
		foreach my $product (sort keys %smashprods) {
			next if (UpdateInfoReader::blacklistedproduct($product));

			my %pkgs = %{$smashprods{$product}};
			foreach my $pkg (keys %pkgs) {
				if (	($pkgs{$pkg} eq "Not affected")		||
					($pkgs{$pkg} eq "Affected")		||
					($pkgs{$pkg} eq "Won't fix")		||
					($pkgs{$pkg} eq "Already fixed")
				) {
					print STDERR "$cve - $product - $pkg - $pkgs{$pkg}\n" if -t STDERR;
					$allproducts{$product} = 1;
					foreach my $binpkg (sort keys %{$UpdateInfoReader::product2packages{$product}->{$pkg}}) {
						$packages{"$binpkg"} = 1;	# no version
					}
					$packages{"$pkg"} = 1;	# no version ... just add source rpm too
				}
			}
		}

		my %containingproducts = ();

		foreach my $product (keys %allproducts) {
			next if (UpdateInfoReader::blacklistedproduct($product));
			my @contained = contained($product);
			if (@contained) {
				foreach my $contained (@contained) {
					$containingproducts{$contained}->{$product} = 1;
				}
			} else {
				$containingproducts{$product}->{$product} = 1;
			}
		}

		# First list of products fixed by the patches of this notice.
		foreach my $baseproduct (sort keys %containingproducts) {
			$writer->startTag("Branch", "Type" => "Product Family", "Name" => $baseproduct);
				foreach my $product (sort keys %{$containingproducts{$baseproduct}}) {
					next if (UpdateInfoReader::blacklistedproduct($product));
					$writer->startTag("Branch", "Type" => "Product Name", "Name" => $product);
						my @dataelement = (
							"FullProductName",	$product,
							"ProductID" => $product,
						);
						if (&SMASHData::get_cpe($product)) {
							push @dataelement,'CPE',&SMASHData::get_cpe($product);
						} else {
							if (($product !~ /^Image/) && ($product !~ /^Container/)) {
								warn "no cpe found for $product in $cve\n";
							}
						}
						$writer->dataElement(@dataelement);

					$writer->endTag("Branch");
				}
			$writer->endTag("Branch");
		}

		# Find and declared affectedness for all chost images...

		my @allimages = keys %PInt::image2cves;
		my @cveallimages = keys %{$PInt::cve2images{$cve}};
		my %chostaffected = ();

		if (@cveallimages) {
			foreach my $image (keys %PInt::images) {
				my %ximage = %{$PInt::images{$image}};

				print STDERR "regexp of $image: " . $ximage{"regexp"} . "\n" if -t STDERR;
				my @cvechostimages = grep(/$ximage{"regexp"}/, @cveallimages);
				my @chostimages = grep(/$ximage{"regexp"}/, @allimages);

				my $fixed = 0;
				foreach my $chost (sort @chostimages) {
					if ($fixed) {
						$chostaffected{$chost} = "Fixed";
						print STDERR "$chost -> Fixed\n" if -t STDERR;
						next;
					}
					if (grep(/$chost/,@cvechostimages)) {
						print STDERR "$chost -> FIRST Fixed\n" if -t STDERR;
						$chostaffected{$chost} = "Fixed";	# First Fixed
						$fixed = 1;
						next;
					}
					print STDERR "$chost -> Known Affected\n" if -t STDERR;
					$chostaffected{$chost} = "Known Affected";
				}
			}

			print STDERR "All chost states:\n" . Dumper(\%chostaffected) . "\n" if -t STDERR;

			###### new chost, lets try this tree:
			# CHOST Family
				# CHOST image name (SLES 15 SP2 CHOST for AWS)
					# CHOST image id

			if (%chostaffected) {
				$writer->startTag("Branch", "Type" => "Product Family", "Name" => "CHOST Images");
					foreach my $name (sort keys %chostnames) {
						if (grep(/$name/,keys %chostaffected)) {
							$writer->startTag("Branch", "Type" => "Product Name", "Name" => $chostnames{$name});
								foreach my $chost (sort grep(/$name/,keys %chostaffected)) {
									my $cname = $chost;
									my $pname = $chost;
									my $sp = "";

									$pname =~ s/\/.*//;
									$cname =~ s/.*\///;

									if ($cname =~ /(sp\d)/) {
										$sp = "$1:";
									}
			
									$writer->dataElement(
										"FullProductName",
										"$chostnames{$name} $cname",
										"ProductID" => "chost:$pname/$cname",
										"CPE" => "cpe:/o:suse:sles:15:$sp" . "chost-$pname:$cname",
									);
								}
							$writer->endTag("Branch");
						}
					}
				$writer->endTag("Branch");
			}
		}

		# Second lists all package-versions referenced...
		foreach my $pkgver (sort keys %packages) {
			$writer->startTag("Branch", "Type" => "Product Version", "Name" => $pkgver);
				my @elements = ( "FullProductName", $pkgver, "ProductID" => $pkgver);
				if (package2cpe($pkgver)) {
					push @elements,"CPE",package2cpe($pkgver);
				}
				$writer->dataElement(@elements);
			$writer->endTag("Branch");
		}

		# Now list all products / package versions.
		if (defined($UpdateInfoReader::products{$cve})) {
			my %xproducts = %{$UpdateInfoReader::products{$cve}};

			foreach my $product (sort keys %xproducts) {
				next if (UpdateInfoReader::blacklistedproduct($product));
				if (!defined($xproducts{$product}->{'packages'})) {
					print STDERR "$cve: no packages for $product, but entry?\n";
					next;
				}
				my %pkgs = %{$xproducts{$product}->{'packages'}};

				foreach my $pkg (sort keys %pkgs) {
					my $pkgver = "$pkg-".$pkgs{$pkg};

					$writer->startTag(
						"Relationship",
						"ProductReference" => $pkgver,
						"RelationType" => "Default Component Of",
						"RelatesToProductReference" => $product
					);
						$writer->dataElement(
							"FullProductName",
							"$pkgver as a component of $product",
							"ProductID" => "$product:$pkgver"
						);
					$writer->endTag("Relationship");
				}
			}
		}
		if (defined($UpdateInfoReader::productsinqa{$cve})) {
			my %xproducts = %{$UpdateInfoReader::productsinqa{$cve}};

			foreach my $product (sort keys %xproducts) {
				next if (UpdateInfoReader::blacklistedproduct($product));
				next unless (defined($xproducts{$product}->{'packages'}));

				my %pkgs = %{$xproducts{$product}->{'packages'}};

				foreach my $pkg (sort keys %pkgs) {
					my $pkgver = "$pkg-".$pkgs{$pkg};

					$writer->startTag(
						"Relationship",
						"ProductReference" => $pkgver,
						"RelationType" => "Default Component Of",
						"RelatesToProductReference" => $product
					);
						$writer->dataElement(
							"FullProductName",
							"$pkgver as a component of $product",
							"ProductID" => "$product:$pkgver"
						);
					$writer->endTag("Relationship");
				}
			}
		}

		foreach my $product (sort keys %smashprods) {
			next if (UpdateInfoReader::blacklistedproduct($product));

			my %pkgs = %{$smashprods{$product}};
			foreach my $pkg (sort keys %pkgs) {

				if (	($pkgs{$pkg} eq "Not affected")     ||
					($pkgs{$pkg} eq "Affected")         ||
					($pkgs{$pkg} eq "Won't fix")         ||
					($pkgs{$pkg} eq "Already fixed")
				) {
					print STDERR "$cve - $product - $pkg - $pkgs{$pkg}\n" if -t STDERR;

					my $havepkg = 0;
					foreach my $binpkg (sort keys %{$UpdateInfoReader::product2packages{$product}->{$pkg}}) {
						$writer->startTag(
							"Relationship",
							"ProductReference" => $binpkg,
							"RelationType" => "Default Component Of",
							"RelatesToProductReference" => $product
						);
							$writer->dataElement(
								"FullProductName",
								"$binpkg as a component of $product",
								"ProductID" => "$product:$binpkg"
							);
						$writer->endTag("Relationship");
						$havepkg = 1 if ($binpkg eq $pkg);
					}
					if (!$havepkg) {
						$writer->startTag(
							"Relationship",
							"ProductReference" => $pkg,
							"RelationType" => "Default Component Of",
							"RelatesToProductReference" => $product
						);
							$writer->dataElement(
								"FullProductName",
								"$pkg as a component of $product",
								"ProductID" => "$product:$pkg"
							);
						$writer->endTag("Relationship");
					}
				}
			}
		}

	$writer->endTag("ProductTree");

	# we just have 1 vulnerability in here.
	$writer->startTag("Vulnerability", "Ordinal" => "1", "xmlns" => "http://docs.oasis-open.org/csaf/ns/csaf-cvrf/v1.2/vuln");
		$writer->startTag("Notes");
			$writer->dataElement(
				"Note",
				$desc || "unknown",
				"Title" => "Vulnerability Description",
				"Type" => "General", 
				"Ordinal" => 1,         # FIXME once we have more than one note
				"xml:lang" => "en"
			);
		$writer->endTag("Notes");

		$writer->dataElement("CVE",$cve);

		$writer->startTag("ProductStatuses");
			if (defined($UpdateInfoReader::products{$cve})) {
				my %xproducts = %{$UpdateInfoReader::products{$cve}};

				$writer->startTag("Status","Type" => "Fixed");
				foreach my $product (sort keys %xproducts) {
					if (!defined($xproducts{$product}->{'packages'})) {
						print STDERR "$cve: no packages for $product, but entry?\n";
						next;
					}
					my %pkgs = %{$xproducts{$product}->{'packages'}};

					foreach my $pkg (sort keys %pkgs) {
						my $pkgver = "$pkg-".$pkgs{$pkg};

						$writer->dataElement("ProductID","$product:$pkgver");
					}
				}
				$writer->endTag("Status","Type" => "Fixed");
			}

			if (defined($UpdateInfoReader::productsinqa{$cve})) {
				my %xproducts = %{$UpdateInfoReader::productsinqa{$cve}};

				$writer->startTag("Status","Type" => "First Fixed");	# tricky which to use
				foreach my $product (sort keys %xproducts) {
					if (!defined($xproducts{$product}->{'packages'})) {
						print STDERR "$cve: no packages for $product, but entry?\n";
						next;
					}
					my %pkgs = %{$xproducts{$product}->{'packages'}};

					foreach my $pkg (sort keys %pkgs) {
						my $pkgver = "$pkg-".$pkgs{$pkg};

						$writer->dataElement("ProductID","$product:$pkgver");
					}
				}
				$writer->endTag("Status","Type" => "Fixed");
			}

			my @notaffectedpkgs = ();
			my @affectedpkgs = ();
			my @wontfixpkgs = ();

			# list not affected ones
			my @pkgs = ();
			foreach my $product (sort keys %smashprods) {
				next if (UpdateInfoReader::blacklistedproduct($product));

				my %pkgs = %{$smashprods{$product}};
				foreach my $pkg (keys %pkgs) {

					if (	($pkgs{$pkg} eq "Not affected")     ||
						($pkgs{$pkg} eq "Already fixed")
					) {
						print STDERR "$cve - $product - $pkg - $pkgs{$pkg}\n" if -t STDERR;
						my $havepkg = 0;
						foreach my $binpkg (sort keys %{$UpdateInfoReader::product2packages{$product}->{$pkg}}) {
							push @notaffectedpkgs, "$product:$binpkg";
							$havepkg = 1 if ($binpkg eq $pkg);
						}
						if (!$havepkg) {
							push @notaffectedpkgs, "$product:$pkg";
						}
					}
					if ($pkgs{$pkg} eq "Affected") {
						print STDERR "$cve - $product - $pkg - $pkgs{$pkg}\n" if -t STDERR;
						my $havepkg = 0;
						foreach my $binpkg (sort keys %{$UpdateInfoReader::product2packages{$product}->{$pkg}}) {
							push @pkgs, "$product:$binpkg";
							$havepkg = 1 if ($binpkg eq $pkg);
						}
						if (!$havepkg) {
							push @pkgs, "$product:$pkg";
						}
					}
					if ($pkgs{$pkg} eq "Won't fix") {
						print STDERR "$cve - $product - $pkg - $pkgs{$pkg}\n" if -t STDERR;
						my $havepkg = 0;
						foreach my $binpkg (sort keys %{$UpdateInfoReader::product2packages{$product}->{$pkg}}) {
							push @wontfixpkgs, "$product:$binpkg";
							$havepkg = 1 if ($binpkg eq $pkg);
						}
						if (!$havepkg) {
							push @wontfixpkgs, "$product:$pkg";
						}
					}
				}
			}
			if (@notaffectedpkgs) {
				$writer->startTag("Status","Type" => "Known Not Affected");
				foreach my $pkg (sort @notaffectedpkgs) {
					$writer->dataElement("ProductID",$pkg);
				}
				$writer->endTag("Status","Type" => "Known Not Affected");
			}
			if (@affectedpkgs) {
				$writer->startTag("Status","Type" => "Known Affected");
				foreach my $pkg (sort @affectedpkgs) {
					$writer->dataElement("ProductID",$pkg);
				}
				$writer->endTag("Status","Type" => "Known Affected");
			}

			if (@wontfixpkgs) {
				$writer->startTag("Status","Type" => "Will Not Fix");
				foreach my $pkg (sort @wontfixpkgs) {
					$writer->dataElement("ProductID",$pkg);
				}
				$writer->endTag("Status","Type" => "Will Not Fix");
			}


			if (%chostaffected) {
				my @fixed = ();
				my @affected = ();
				foreach my $name (sort keys %chostnames) {
					foreach my $chost (sort grep(/$name/,keys %chostaffected)) {
						my $cname = $chost;
						my $pname = $chost;

						$pname =~ s/\/.*//;
						$cname =~ s/.*\///;

						if ($chostaffected{$chost} eq "Fixed") {
							push @fixed, "chost:$pname/$cname";
						} else {
							push @affected, "chost:$pname/$cname";
						}
					}
				}
				if (@fixed) {
					$writer->startTag("Status","Type" => "Fixed");
					foreach my $chost (sort @fixed) {
						$writer->dataElement("ProductID",$chost);
					}
					$writer->endTag("Status","Type" => "Fixed");
				}
				if (@affected) {
					$writer->startTag("Status","Type" => "Known Affected");
					foreach my $chost (sort @affected) {
						$writer->dataElement("ProductID",$chost);
					}
					$writer->endTag("Status","Type" => "Known Affected");
				}
			}
		$writer->endTag("ProductStatuses");

		$writer->startTag("Threats");
			$writer->startTag("Threat", "Type" => "Impact");
				my $severity = $SMASHData::severity{$cve};
				if (!defined($severity)) {
					print STDERR "$cve - no severity?\n" if (-t STDERR);
					$severity = "moderate";
				}
				$writer->dataElement("Description", $severity);
			$writer->endTag("Threat");
		$writer->endTag("Threats");

		my $cvss = &SMASHData::get_cvss_issue($cve);
		my $cvss3 = &SMASHData::get_cvssv3_issue($cve);
		if (defined($cvss) || defined($cvss3)) {
			my %cvss = %{$cvss};
			my %cvss3 = %{$cvss3};

			if (	defined($cvss{'SUSE'}) || defined($cvss{'National Vulnerability Database'}) ||
				defined($cvss3{'SUSE'}) || defined($cvss3{'National Vulnerability Database'})
			) {
				$writer->startTag("CVSSScoreSets");
					# emit either SUSE or NVD cvss v2
					if (defined($cvss{'SUSE'})) {
						$writer->startTag("ScoreSetV2");
							$writer->dataElement("BaseScoreV2", filter_score($cvss{'SUSE'}->{base_score}));
							$writer->dataElement("VectorV2", $cvss{'SUSE'}->{base_vector});
						$writer->endTag("ScoreSetV2");
					} else  {
						if (defined($cvss{'National Vulnerability Database'})) {
							$writer->startTag("ScoreSetV2");
								$writer->dataElement("BaseScoreV2", filter_score($cvss{'National Vulnerability Database'}->{base_score}));
								$writer->dataElement("VectorV2", $cvss{'National Vulnerability Database'}->{base_vector});
							$writer->endTag("ScoreSetV2");
						}
					}
					if (defined($cvss3{'SUSE'})) {
						$writer->startTag("ScoreSetV3");
							$writer->dataElement("BaseScoreV3", filter_score($cvss3{'SUSE'}->{base_score}));
							$writer->dataElement("VectorV3", $cvss3{'SUSE'}->{base_vector});
						$writer->endTag("ScoreSetV3");
					} else {
						if (defined($cvss3{'National Vulnerability Database'})) {
							$writer->startTag("ScoreSetV3");
								$writer->dataElement("BaseScoreV3", filter_score($cvss3{'National Vulnerability Database'}->{base_score}));
								$writer->dataElement("VectorV3", $cvss3{'National Vulnerability Database'}->{base_vector});
							$writer->endTag("ScoreSetV3");
						}
					}

				$writer->endTag("CVSSScoreSets");
			}
		}


	$writer->endTag("Vulnerability");

	$writer->endTag("cvrfdoc");
	$writer->end();

	$output->close();

	# was generated earlier ... 
	die ">$fn.new is 0 bytes, disk full?" if (! -s "$fn.new");
	system("xmllint --format $fn.new >$fn.new.formatted");
	my $oldcvrf;
	my $newcvrf;

	if (! -s "$fn.new.formatted") {
		#unlink("$fn.new");
		#unlink("$fn.new.formatted");
		die ">$fn.new.formatted is 0 bytes, conversion failed or disk full?";
		next;
	}

	# Magic to update the RevisionHistory, but only if something outside of the block changed.

#    <RevisionHistory>
#      <Revision>
#        <Number>1</Number>
#        <Date>2021-05-30T14:48:27Z</Date>
#        <Description>current</Description>
#      </Revision>
#    </RevisionHistory>
#    <InitialReleaseDate>2021-05-30T14:48:27Z</InitialReleaseDate>
#    <CurrentReleaseDate>2021-05-30T14:48:27Z</CurrentReleaseDate>


	my $newstate = "";
	my $newbefore = "";
	my $newafter = "";
	my $oldstate = "";
	my $oldbefore = "";
	my $oldafter = "";
	my $state = 0;
	my $revision = 0;
	my $currelease = "";
	my $curdate = "";
	if (open(CVRF,"<$fn.new.formatted")) {
		$state = 0;
		while (<CVRF>) {
			if (/<RevisionHistory>/) {
				$state = 1;
				$newstate = $_;
				next;
			}
			if ($state == 1) {
				if (/<CurrentReleaseDate>/) {
					$state = 2;	# exit this node after this line
					$currelease = $_;
				}
				if (/<Date>/) {
					$curdate = $_;
				}
				$newstate .= $_;
				next;
			}
			if ($state == 0) {
				$newbefore .= $_;
				next;
			}
			if ($state == 2) {
				$newafter .= $_;
				next;
			}
		}
		close(CVRF);
	}
	if (open(CVRF,"<$fn")) {
		$state = 0;
		while (<CVRF>) {
			if (/<RevisionHistory>/) {
				$state = 1;
				$oldstate = $_;
				next;
			}
			if ($state == 1) {
				# we postprocess this revision blob as it would already contain the changes...
				if (/<CurrentReleaseDate>/) {
					$state = 2;
					# we ALWAYS use the one from the new...
					$oldstate .= $currelease;
					next;
				}
				if (/<Date>/) {
					# we ALWAYS use the one from the new...
					$oldstate .= $curdate;
					next;
				}
				if (/<Number>(\d*)<\/Number>/) {
					my $num = $1 + 1;
					s/<Number>(\d*)<\/Number>/<Number>$num<\/Number>/;
				}
				$oldstate .= $_;
				next;
			}
			if ($state == 0) {
				$oldbefore .= $_;
				next;
			}
			if ($state == 2) {
				$oldafter .= $_;
				next;
			}
		}
		close(CVRF);
	} else {
		# if no old, just use the new as-is
		rename("$fn.new.formatted",$fn);
		unlink("$fn.new");
		next;
	}

	# If before or after section changed, emit new data with adjusted "old" revision blob
	if (($oldbefore ne $newbefore) || ($oldafter ne $newafter)) {
		open(CVRF,">$fn.new");
		print CVRF $newbefore  . $oldstate . $newafter;
		close(CVRF);
		system("diff -uN $fn $fn.new");
		rename("$fn.new",$fn);
	} else {
		unlink("$fn.new");
	}
	unlink("$fn.new.formatted");
}

system("cd $cvrfdir/../;tar cjf cvrf-cve.tar.bz2 cvrf-cve");

print "SUCCESS\n";
