#!/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);

use CGI;
my $cgi = new CGI;

use POSIX qw/strftime setlocale LC_TIME/;
use List::MoreUtils qw/uniq/;
require CanDBReader;
require UpdateInfoReader;
require SMASHData;

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

# major -> max sp
my %maxsp = (
	'11' => 4,
	'12' => 5,
	'15' => 2,
);

my %prod2package = ();

sub generate_yaml($$) {
	my ($productver,$modulever) = @_;

	my $productmatch;
	my $patchfn;
	my $vulnfn;
	my $ovalprefix;
	my $defserial;
	my $ovaltst;
	my $ovalobj;
	my $ovalsta;
	# products for which we want to dump oval
	my @products = grep (/($productver|$productver-LTSS|Module.*$modulever)$/,keys %UpdateInfoReader::patches);

	print STDERR "looking for product: $productver, modulever: $modulever\n";
	print STDERR "generating for: " . join("\n",@products) if (-t STDERR);

	my %allpatches;

# we want
# product -> package -> version -> CVEs
# package -> { package -> version -> [CVE] }

	# collect all patches we want to do by product (SLED/SLES, various SPs)
	foreach my $product (@products) {
		print "scanning patches for $product\n";
		my %patches = %{$UpdateInfoReader::patches{$product}};

		foreach my $patch (keys %patches) {
			if ($UpdateInfoReader::patchinqa{$patch}) {
				print STDERR "$patch is in QA\n" if (-t STDERR);
				next;
			}
			$allpatches{$patch} = 1;
		}
	}

	my %allpackages = ();

	foreach my $patch (sort keys %allpatches) {
		my @affectedproducts = ();
		my @productcrita = ();

		if (defined($UpdateInfoReader::patchtype{$patch})) {
			next if ($UpdateInfoReader::patchtype{$patch} ne "security");
		} else {
			print STDERR "type not defined in $patch?\n";
		}

		#print "scanning patch $patch\n";

		# for which products do we have this patch
		foreach my $product (sort @products) {
			my %patches = %{$UpdateInfoReader::patches{$product}};
			next unless (defined($patches{$patch}));

			push @affectedproducts,$product;

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

			# skip teradata kernel packages, as they have a different naming scheme.
			next if (($product =~ /TERADATA/i) && grep (/kernel-/,keys %packages));

			foreach my $pkg (sort keys %packages) {
				next if ($pkg =~ /debuginfo/);
				next if ($pkg =~ /debugsource/);

				my $ver = $packages{$pkg};

				my %ver2cve = ();

				if (defined($allpackages{$pkg})) {
					%ver2cve = %{$allpackages{$pkg}};
				}
				my %cvelist = ();
				if (defined($ver2cve{$ver})) {
					%cvelist = %{$ver2cve{$ver}};
				}

				foreach my $cve (sort keys $UpdateInfoReader::patchreferences{$patch}) {
					if ($cve =~ /CVE-/) {
						$cvelist{$cve} = 1;
					}
				}
				if (%cvelist) {
					$ver2cve{$ver} = \%cvelist;
				}
				if (%ver2cve) {
					$allpackages{$pkg} = \%ver2cve;
				}
			}
		}
	}

	print STDERR "grepping for $productver|Module.*$modulever\n" if -t STDERR;

	foreach my $smashprod (grep (/($productver|$productver LTSS|Module.*$modulever)$/,keys %prod2package)) {
		my %pkg2cve = %{$prod2package{$smashprod}};

		print STDERR "unaffected mode: scanning for smash product $smashprod\n" if -t STDERR;
		foreach my $spkg (keys %pkg2cve) {
			my %cve2state = %{$pkg2cve{$spkg}};

			#print STDERR "$spkg -> " . join(",",keys  %{$UpdateInfoReader::gasrpm2rpm{$smashprod}->{$spkg}}) . "\n" if -t STDERR;
			foreach my $pkg (sort keys %{$UpdateInfoReader::gasrpm2rpm{$smashprod}->{$spkg}}) {
				#print STDERR "\t$pkg\n" if -t STDERR;
				foreach my $cve (keys %cve2state) {
					if (	($cve2state{$cve} eq "Not affected") ||
						($cve2state{$cve} eq "Already fixed")
					) {
						#print STDERR "\t\ŧ$cve\n" if -t STDERR;
						my %ver2cve = ();
						if (defined($allpackages{$pkg})) {
							%ver2cve = %{$allpackages{$pkg}};
						}
						my %cves = ();
						if (defined($ver2cve{'not-affected'})) {
							%cves = %{$ver2cve{'not-affected'}};
						}
						if (defined($UpdateInfoReader::gapackages{$smashprod}->{$pkg})) {
							$cves{$cve} = $UpdateInfoReader::gapackages{$smashprod}->{$pkg};
						} else {
							$cves{$cve} = 0;
						}
						$ver2cve{'not-affected'} = \%cves;
						$allpackages{$pkg} = \%ver2cve;
					}
				}
			}
		}
	}

	print YAML "  packages:\n";
	foreach my $pkg (sort keys %allpackages) {
		print YAML "    $pkg:\n";
		print YAML "      fixed:\n";
		my %ver2cve = %{$allpackages{$pkg}};
		foreach my $ver (sort keys %ver2cve) {
			my %cves = %{$ver2cve{$ver}};

			next if ($ver eq "not-affected");

			if (%cves) {
				print YAML "        $ver:\n";
				foreach my $cve (sort keys %cves) {
					print YAML "          - $cve\n";
				}
			}
		}
		if (defined($ver2cve{'not-affected'})) {
			my %cves = %{$ver2cve{'not-affected'}};
			if (%cves) {
				my %versions = ();
				print YAML "      not-present:\n";
				# flip the cve -> version array to version -> cves
				foreach my $cve (sort keys %cves) {
					if (defined($versions{$cves{$cve}})) {
						$versions{$cves{$cve}} .= ",$cve";
					} else {
						$versions{$cves{$cve}}  = $cve;
					}
				}
				foreach my $version (sort keys %versions) {
					print YAML "        $version:\n";
					foreach my $cve (sort split(/,/,$versions{$version})) {
						if (defined($CanDBReader::bugzillas{$cve})) {
							my @bugs = sort split(/,/,$CanDBReader::bugzillas{$cve});
							my $bug = shift @bugs;
							print YAML "        - $cve: see https://bugzilla.suse.com/$bug\n";
						} else {
							print YAML "        - $cve\n";
						}
					}
				}
			}
		}
	}
}

# remapping smash data
## read all (cached) smash issues.
my @allcves = <smash/issue/CVE*.0>;
foreach my $cve (@allcves) {
        $cve =~ s/.0$//;
        $cve =~ s/smash\/issue\///;
	read_smash_issue($cve,0);
}

## remap the database layout so we can use it better

# { CVE -> { PROD -> { PKG -> STATE } } } 
# turn into
# prod -> package -> cve -> state
system("date");
print STDERR "remapping SMASH STATE\n";
foreach my $cve (keys %SMASHData::pkgstate) {
	my %prods = %{$SMASHData::pkgstate{$cve}};

	foreach my $prod (keys %prods) {
		my %pkgstates = %{$prods{$prod}};

		$prod =~ s/ GA$//;

		my %pkg2cve = ();
		if (defined($prod2package{$prod})) {
			%pkg2cve = %{$prod2package{$prod}};
		}

		foreach my $pkg (keys %pkgstates) {
			my %cve = ();
			if (defined($pkg2cve{$pkg})) {
				%cve = %{$pkg2cve{$pkg}};
			}
			$cve{$cve} = $pkgstates{$pkg};
			$pkg2cve{$pkg} = \%cve;
		}
		$prod2package{$prod} = \%pkg2cve;
	}
}
print STDERR "remapping SMASH STATE .. done\n";
#print STDERR Dumper(\%prod2package);
system("date");
print STDERR "products: " . join(",", sort keys %prod2package) . "\n";

umask 022;
foreach my $major (sort keys %maxsp) {
	my $maxsp = $maxsp{$major};

	for (my $sp = 0; $sp <= $maxsp; $sp++) {
		print STDERR "$major - $sp\n";

		my $prodver = "$major SP$sp";
		$prodver = "$major" if ($sp == 0);
		my $modulever = "$major SP$sp";
		$modulever = "$major" if ($sp == 0);
		$modulever = "12" if ($major eq '12');

		my $fn = "$path/backports-sle$major-sp$sp.yaml";

		open(YAML,">$fn.new")||die "open $fn:$!\n";
		print YAML "---\n";
		print YAML "- name: SLES\n";
		print YAML "  version: $prodver\n";
		generate_yaml($prodver, $modulever);
		close(YAML)||die "closing $fn:$!\n";

		if (system("cmp $fn $fn.new >/dev/null")) {
			my @difflines = `diff -u $fn $fn.new`;
			my $pluslines = 0;
			print @difflines; # debug diff
			rename("$fn.new",$fn);
		} else {
			unlink("$fn.new");
		}
	}
}

print "SUCCESS\n";
