#!/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;
my $cverepobase=`dirname $dn`;
chomp($cverepobase);

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

use POSIX qw/strftime setlocale LC_TIME/;

require CanDBReader;
require CVEListReader;
require UpdateInfoReader;
UpdateInfoReader->import_product_updates();
UpdateInfoReader->import_images();
require SMASHData;
require PInt;

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

my %containerids = ();
open(CONTAINERIDS,"<$cverepobase/data/containerid")||die;
while (<CONTAINERIDS>) {
	chomp;
	my ($image,$id) = split(/;/);
	$containerids{$id} = $image;

	# we have ids without provider prefix ... look for the corect provider prefix
	next if (defined($PInt::image2url{$image}));

	foreach my $provider ("google", "microsoft", "alibaba", "amazon", "oracle") {
		if (defined($PInt::image2url{"google/$image"})) {
			$image = "google/$image";
			$containerids{$id} = $image;
			last;
		}
	}
}
close(CONTAINERIDS);

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

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

print "generating Image CVRF content at " . localtime() . "\n";

foreach my $image (sort keys %containerids) {
	my $containerid = $containerids{$image};
	my %xx;
	my @xx;
	my $str;
	my $released_products = "";
	my %products;

	next unless ($image =~ /SUSE-IU/);
	next unless ($PInt::image2url{$containerid});

	print STDERR "generating for $image containerid $containerid\n" if (-t STDERR);

	my $sp = $containerid;

	if ($sp =~ /sles-(\d\d-sp\d)/) {
		$sp = $1;
	} else {
		if ($sp =~ /sles-(\d\d)-/) {
			$sp = $1;
		} else {
			if ($sp =~ /suse-manager-4-0/) {
				$sp = "15-sp1";
			} else {
				if ($sp =~ /suse-manager-4-1/) {
					$sp = "15-sp2";
				} else {
					if ($sp =~ /suse-manager-4-2/) {
						$sp = "15-sp3";
					} else {
						warn "cannot parse $containerid for service pack";
					}
				}
			}
		}
	}
	$sp =~ s/-/./;
	print STDERR "	service pack: $sp\n";

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

	foreach my $cvrfver ("1.1", "1.2") {
		my $xpath = $cvrfdir;
		if ($cvrfver eq "1.2") {
			$xpath .= "1.2";
		}

		my $fn = "$xpath/cvrf-\L$image.xml";

		my $output = IO::File->new(">$fn.new");
		my $writer = XML::Writer->new(OUTPUT => $output, NEWLINES => 1, ENCODING => "utf-8");
		$writer->xmlDecl("UTF-8");
		if ($cvrfver eq "1.1") {
			$writer->startTag("cvrfdoc",
				"xmlns" => "http://www.icasi.org/CVRF/schema/cvrf/1.1",
				"xmlns:cvrf" => "http://www.icasi.org/CVRF/schema/cvrf/1.1",
			);
		} else {
			$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", $image, "xml:lang" => "en");
		$writer->dataElement("DocumentType", "SUSE Image");

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

		# FIXME: perhaps read old document, and adjust????

		$writer->startTag("DocumentTracking");

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

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

			my $dt = DateTime->now;

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

			# 
			my $xdate = "20200101";
			if ($containerid =~ /(20\d\d)(\d\d)(\d\d)/) {
				$xdate = "$1-$2-$3";
			}

			$writer->dataElement("InitialReleaseDate","${xdate}T01:00:00Z");
			$writer->dataElement("CurrentReleaseDate","${xdate}T01:00:00Z");

			$writer->startTag("Generator");
				$writer->dataElement("Engine","cve-database/bin/generate-cvrf-publiccloud.pl");
				$writer->dataElement("Date","2021-02-18T01:00:00Z");
			$writer->endTag("Generator");

		$writer->endTag("DocumentTracking");

		$writer->startTag("DocumentNotes");
			$writer->dataElement(
				"Note",
				"Image update for $image / $containerid",
				"Title" => "Topic", "Type" => "Summary", "Ordinal" => "1", "xml:lang" => "en"
			);
			$writer->dataElement(
				"Note",
				$PInt::image2changes{$containerid},
				"Title" => "Details", "Type" => "General", "Ordinal" => "2", "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" => "3", "xml:lang" => "en"
			);
		$writer->endTag("DocumentNotes");

		$writer->startTag("DocumentReferences");
			if (defined($PInt::image2cves->{$containerid})) {
				foreach my $cve (sort keys ($PInt::image2cves->{$containerid})) {
					$writer->startTag("Reference", "Type" => "Self");
						$writer->dataElement("URL", "https://www.suse.com/security/cve/$cve");
						$writer->dataElement("Description", "SUSE CVE page for $cve");
					$writer->endTag("Reference");
				}
			}
			$writer->startTag("Reference", "Type" => "Self");
				$writer->dataElement("URL", $PInt::image2url{$containerid});
				$writer->dataElement("Description", "Public Cloud Image Info");
			$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");

		if ($cvrfver eq "1.1") {
			$writer->startTag("ProductTree", "xmlns" => "http://www.icasi.org/CVRF/schema/prod/1.1");
		} else {
			$writer->startTag("ProductTree", "xmlns" => "http://docs.oasis-open.org/csaf/ns/csaf-cvrf/v1.2/prod");
		}

		my $product = "Public Cloud Image $containerid";

		# print STDERR "Packages in $containerid: " . join(",",sort keys %{$PInt::image2pkg2vers{$containerid}}) . "\n";

			# First list of products fixed by the patches of this notice.
			$writer->startTag("Branch", "Type" => "Product Family", "Name" => $product);
				$writer->startTag("Branch", "Type" => "Product Name", "Name" => $product);
					$writer->dataElement(
						"FullProductName",
						$product,	# FIXME map to correct container
						"ProductID" => $product
					);
				$writer->endTag("Branch");
			$writer->endTag("Branch");

			# Second lists all package-versions referenced...
			foreach my $package (sort keys %{$PInt::image2pkg2vers{$containerid}}) {
				my $pkgver = "$package-" . $PInt::image2pkg2vers{$containerid}->{$package};
				$writer->startTag("Branch", "Type" => "Product Version", "Name" => $pkgver);
					$writer->dataElement( "FullProductName", $pkgver, "ProductID" => $pkgver);
				$writer->endTag("Branch");
			}

			# Now list all products / package versions.

			foreach my $package (sort keys %{$PInt::image2pkg2vers{$containerid}}) {
				my $pkgver = "$package-" . $PInt::image2pkg2vers{$containerid}->{$package};
				$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");
			}

		$writer->endTag("ProductTree");

		if (defined($PInt::image2cves{$containerid})) {
			foreach my $cve (sort keys (%{$PInt::image2cves{$containerid}})) {
				if ($cvrfver eq "1.1") {
					$writer->startTag("Vulnerability", "Ordinal" => 1, "xmlns" => "http://www.icasi.org/CVRF/schema/vuln/1.1");
				} else {
					$writer->startTag("vuln:Vulnerability", "Ordinal" => 1, "xmlns" => "http://docs.oasis-open.org/csaf/ns/csaf-cvrf/v1.2/vuln");
					# Tags missing: Title, ID
				}
					$writer->startTag("Notes");
						$writer->dataElement(
							"Note",
							get_description($cve) || "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");

						$writer->startTag("Status","Type" => "Fixed");
							# only report the packages for this cve.... the packages below are SOURCE packages, we need

							# we have SOURCE rpms in image2cve2pkgs ... translate them to binary rpms.
							my %pkgs = ();
							foreach my $srcpkg (sort keys %{$PInt::image2cve2pkgs{$containerid}->{$cve}}) {
								print STDERR "looking for source rpm $srcpkg...\n" if -t STDERR;
								foreach my $product (grep /$sp/i, keys %UpdateInfoReader::product2packages) {
									#%UpdateInfoReader::product2packages = ();       # { Product -> { srcpkg -> [ binarypkg1, binarypkg2, binarypkg3] } }    currently without codestreams
									print STDERR "... searching in product $product...\n" if -t STDERR;
									if ($UpdateInfoReader::product2packages{$product}->{$srcpkg}) {
										foreach my $binrpm (keys %{$UpdateInfoReader::product2packages{$product}->{$srcpkg}}) {
											print STDERR "...... have $binrpm ...\n" if -t STDERR;
											# only emit the binary if we have it in the image.
											if ($PInt::image2pkg2vers{$containerid}->{$binrpm}) {
												print STDERR "...... remembering $binrpm ...\n" if -t STDERR;
												$pkgs{$binrpm} = 1;
											}
										}
									}
								}
							}
							foreach my $package (sort keys %pkgs) {
								my $pkgver = "$package-" . $PInt::image2pkg2vers{$containerid}->{$package};

								$writer->dataElement("ProductID","$product:$pkgver");
							}
						$writer->endTag("Status","Type" => "Fixed");
					$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 $cvss2vector = "";
					my $cvss2score = 0;
					# CVSSv2
					my $cvss = get_cvss($cve);
					if (defined($cvss)) {
						my $entry;

						if (defined($cvss->{'SUSE'})) {
							$entry = $cvss->{'SUSE'};
						}
						if (defined($cvss->{'National Vulnerability Database'})) {
							$entry = $cvss->{'National Vulnerability Database'};
						}
						if ($entry->{'base_vector'}) {
							$cvss2vector = $entry->{'base_vector'};
							$cvss2score = $entry->{'base_score'};
						}
					}

					my $cvss3vector = "";
					my $cvss3score = 0;
					# CVSSv3
					$cvss = &SMASHData::get_cvssv3_issue($cve);
					if (defined($cvss)) {
						my $entry;

						if (defined($cvss->{'SUSE'})) {
							$entry = $cvss->{'SUSE'};
						}
						if (defined($cvss->{'National Vulnerability Database'})) {
							$entry = $cvss->{'National Vulnerability Database'};
						}
						if ($entry->{'base_vector'}) {
							$cvss3vector = $entry->{'base_vector'};
							$cvss3score = $entry->{'base_score'};
						}
					}
					if ($cvss2vector ne "") {
						$writer->startTag("CVSSScoreSets");
							$writer->startTag("ScoreSet");
								$writer->dataElement("BaseScore", $cvss2score);
								$writer->dataElement("Vector", $cvss2vector);
							$writer->endTag("ScoreSet");
						$writer->endTag("CVSSScoreSets");
					}

				if ($cvrfver eq "1.1") {
					$writer->endTag("Vulnerability");
				} else {
					$writer->endTag("vuln: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") {
			warn ">$fn.new.formatted is 0 bytes, conversion failed or disk full?";
			unlink("$fn.new");
			unlink("$fn.new.formatted");
			next;
		}

		if (open(CVRF,"<$fn.new.formatted")) {
			$newcvrf = join("",<CVRF>);
			close(CVRF);
		}
		if (open(CVRF,"<$fn")) {
			$oldcvrf = join("",<CVRF>);
			close(CVRF);
		}
		if ($oldcvrf ne $newcvrf) {
			if (system("diff -uN $fn $fn.new.formatted")) {
				rename("$fn.new.formatted",$fn);
				unlink("$fn.new");
			} else {
				print STDERR "diff for $fn not detected by reader?\n";
				unlink("$fn.new");
				unlink("$fn.new.formatted");
			}
		} else {
			unlink("$fn.new");
			unlink("$fn.new.formatted");
		}
	}
}

print "SUCCESS\n";
