#!/usr/bin/perl -w
# oval generator
# Users:
# 	Anchore Grype , DB collector is at https://github.com/anchore/vunnel
# 	DISA stig itself -> SCAP profile of SUSE SLES references the URLs
# 	Neuvector
#	Qualys

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;
UpdateInfoReader->import_product_updates();
UpdateInfoReader->import_images();
require CVEListReader;
require SMASHData;

require ModuleContained;


########### configuration what to output in the index.html #######################

my %productnames = (
	'opensuse.tumbleweed' => "openSUSE Tumbleweed",	#patch does not make sense
	'opensuse.leap.micro.5.2' => "openSUSE Leap Micro 5.2",
	'opensuse.leap.micro.5.2-patch' => "openSUSE Leap Micro 5.2 - by patches",
	'opensuse.leap.micro.5.3' => "openSUSE Leap Micro 5.3",
	'opensuse.leap.micro.5.3-patch' => "openSUSE Leap Micro 5.3 - by patches",
	'opensuse.leap.micro.5.4' => "openSUSE Leap Micro 5.4",
	'opensuse.leap.micro.5.4-patch' => "openSUSE Leap Micro 5.4 - by patches",
	'opensuse.leap.micro.5.5' => "openSUSE Leap Micro 5.5",
	'opensuse.leap.micro.5.5-patch' => "openSUSE Leap Micro 5.5 - by patches",
	'opensuse.leap.15.6' => "openSUSE Leap 15.6",
	'opensuse.leap.15.6-patch' => "openSUSE Leap 15.6 - by patches",
	'opensuse.leap.15.5' => "openSUSE Leap 15.5",
	'opensuse.leap.15.5-patch' => "openSUSE Leap 15.5 - by patches",
	'opensuse.leap.15.4' => "openSUSE Leap 15.4",
	'opensuse.leap.15.4-patch' => "openSUSE Leap 15.4 - by patches",
	'opensuse.leap.15.3' => "openSUSE Leap 15.3",
	'opensuse.leap.15.3-patch' => "openSUSE Leap 15.3 - by patches",
	'opensuse.leap.15.2' => "openSUSE Leap 15.2",
	'opensuse.leap.15.2-patch' => "openSUSE Leap 15.2 - by patches",
	'opensuse.leap.15.1' => "openSUSE Leap 15.1",
	'opensuse.leap.15.1-patch' => "openSUSE Leap 15.1 - by patches",
	'opensuse.leap.15.0' => "openSUSE Leap 15.0",
	'opensuse.leap.15.0-patch' => "openSUSE Leap 15.0 - by patches",
	'opensuse.leap.42.3' => "openSUSE Leap 42.3",
	'opensuse.leap.42.3-patch' => "openSUSE Leap 42.3 - by patches",
	'opensuse.leap.42.2' => "openSUSE Leap 42.2",
	'opensuse.leap.42.2-patch' => "openSUSE Leap 42.2 - by patches",
	'opensuse.leap.42.1' => "openSUSE Leap 42.1",
	'opensuse.leap.42.1-patch' => "openSUSE Leap 42.1 - by patches",
	'opensuse.13.2' => "openSUSE 13.2",
	'opensuse.13.2-patch' => "openSUSE 13.2 - by patches",
	'opensuse.13.1' => "openSUSE 13.1",
	'opensuse.13.1-patch' => "openSUSE 13.1 - by patches",
	'opensuse.12.3' => "openSUSE 12.3",
	'opensuse.11.4' => "openSUSE 11.4",
	'opensuse.11.3' => "openSUSE 11.3",
	'opensuse.11.2' => "openSUSE 11.2",
	'opensuse.11.1' => "openSUSE 11.1",
	'opensuse.11.0' => "openSUSE 11.0",
	'opensuse.10.3' => "openSUSE 10.3",
	'opensuse.10.2' => "SUSE Linux 10.2",
	'suse.linux.enterprise.server.10' => "SUSE Linux Enterprise Server 10 (all Service Packs)",
	'suse.linux.enterprise.server.11' => "SUSE Linux Enterprise Server 11 (all Service Packs)",
	'suse.linux.enterprise.server.11-patch' => "SUSE Linux Enterprise Server 11 (all Service Packs) - by patches",
	'suse.linux.enterprise.server.12' => "SUSE Linux Enterprise Server 12 (all Service Packs)",
	'suse.linux.enterprise.server.12-patch' => "SUSE Linux Enterprise Server 12 (all Service Packs) - by patches",
	'suse.linux.enterprise.server.15' => "SUSE Linux Enterprise Server 15 (all Service Packs)",
	'suse.linux.enterprise.server.15-patch' => "SUSE Linux Enterprise Server 15 (all Service Packs) - by patches",
	'suse.linux.enterprise.12' => "SUSE Linux Enterprise 12 (all products)",
	'suse.linux.enterprise.12-patch' => "SUSE Linux Enterprise 12 (all products) - by patches",
	'suse.linux.enterprise.15' => "SUSE Linux Enterprise 15 (all products)",
	'suse.linux.enterprise.15-patch' => "SUSE Linux Enterprise 15 (all products) - by patches",
	'suse.linux.enterprise.desktop.10' => "SUSE Linux Enterprise Desktop 10 (all Service Packs)",
	'suse.linux.enterprise.desktop.11' => "SUSE Linux Enterprise Desktop 11 (all Service Packs)",
	'suse.linux.enterprise.desktop.11-patch' => "SUSE Linux Enterprise Desktop 11 (all Service Packs) - by patches",
	'suse.linux.enterprise.desktop.12' => "SUSE Linux Enterprise Desktop 12 (all Service Packs)",
	'suse.linux.enterprise.desktop.12-patch' => "SUSE Linux Enterprise Desktop 12 (all Service Packs) - by patches",
	'suse.linux.enterprise.desktop.15' => "SUSE Linux Enterprise Desktop 15 (all Service Packs)",
	'suse.linux.enterprise.desktop.15-patch' => "SUSE Linux Enterprise Desktop 15 (all Service Packs) - by patches",
	'suse.linux.enterprise.micro.5.0' => "SUSE Linux Enterprise Micro 5.0",
	'suse.linux.enterprise.micro.5.0-patch' => "SUSE Linux Enterprise Micro 5.0 - by patches",
	'suse.linux.enterprise.micro.5.1' => "SUSE Linux Enterprise Micro 5.1",
	'suse.linux.enterprise.micro.5.1-patch' => "SUSE Linux Enterprise Micro 5.1 - by patches",
	'suse.linux.enterprise.micro.5.2' => "SUSE Linux Enterprise Micro 5.2",
	'suse.linux.enterprise.micro.5.2-patch' => "SUSE Linux Enterprise Micro 5.2 - by patches",
	'suse.linux.enterprise.micro.5.3' => "SUSE Linux Enterprise Micro 5.3",
	'suse.linux.enterprise.micro.5.3-patch' => "SUSE Linux Enterprise Micro 5.3 - by patches",
	'suse.linux.enterprise.micro.5.4' => "SUSE Linux Enterprise Micro 5.4",
	'suse.linux.enterprise.micro.5.4-patch' => "SUSE Linux Enterprise Micro 5.4 - by patches",
	'suse.linux.enterprise.micro.5.5' => "SUSE Linux Enterprise Micro 5.5",
	'suse.linux.enterprise.micro.5.5-patch' => "SUSE Linux Enterprise Micro 5.5 - by patches",
	'suse.linux.enterprise.micro.6.0' => "SUSE Linux Enterprise Micro 6.0",
	'suse.linux.enterprise.micro.6.0-patch' => "SUSE Linux Enterprise Micro 6.0 - by patches",
	'suse.openstack.cloud.5.12computenode' => 'SUSE Linux Enterprise 12 Compute Node for Cloud 5',
	'suse.openstack.cloud.5.12computenode-patch' => 'SUSE Linux Enterprise 12 Compute Node for Cloud 5 - by patches',
	'suse.openstack.cloud.5' => 'SUSE OpenStack Cloud 5',
	'suse.openstack.cloud.5-patch' => 'SUSE OpenStack Cloud 5 - by patches',
	'suse.openstack.cloud.6' => 'SUSE OpenStack Cloud 6',
	'suse.openstack.cloud.6-patch' => 'SUSE OpenStack Cloud 6 - by patches',
	'suse.openstack.cloud.7' => 'SUSE OpenStack Cloud 7',
	'suse.openstack.cloud.7-patch' => 'SUSE OpenStack Cloud 7 - by patches',
	'suse.openstack.cloud.8' => 'SUSE OpenStack Cloud 8',
	'suse.openstack.cloud.8-patch' => 'SUSE OpenStack Cloud 8 - by patches',
	'suse.openstack.cloud.9' => 'SUSE OpenStack Cloud 9',
	'suse.openstack.cloud.9-patch' => 'SUSE OpenStack Cloud 9 - by patches',
	'suse.manager.4.0' => 'SUSE Manager 4.0',
	'suse.manager.4.0-patch' => 'SUSE Manager 4.0 - by patches',
	'suse.manager.4.1' => 'SUSE Manager 4.1',
	'suse.manager.4.1-patch' => 'SUSE Manager 4.1 - by patches',
	'suse.manager.4.2' => 'SUSE Manager 4.2',
	'suse.manager.4.2-patch' => 'SUSE Manager 4.2 - by patches',
	'suse.manager.4.3' => 'SUSE Manager 4.3',
	'suse.manager.4.3-patch' => 'SUSE Manager 4.3 - by patches',
	'suse.storage.7' => 'SUSE Storage 7',
	'suse.storage.7-patch' => 'SUSE Manager 7 - by patches',
);

# This is for the order in the index page.
my @productlist = (
	'suse.linux.enterprise.server.15',
	'suse.linux.enterprise.server.15-patch',
	'suse.linux.enterprise.desktop.15',
	'suse.linux.enterprise.desktop.15-patch',
	'suse.linux.enterprise.15',
	'suse.linux.enterprise.15-patch',
	'suse.linux.enterprise.server.12',
	'suse.linux.enterprise.server.12-patch',
	'suse.linux.enterprise.12',
	'suse.linux.enterprise.12-patch',
	'suse.linux.enterprise.server.11',
	'suse.linux.enterprise.server.11-patch',
	'suse.linux.enterprise.micro.5.0',
	'suse.linux.enterprise.micro.5.0-patch',
	'suse.linux.enterprise.micro.5.1',
	'suse.linux.enterprise.micro.5.1-patch',
	'suse.linux.enterprise.micro.5.2',
	'suse.linux.enterprise.micro.5.2-patch',
	'suse.linux.enterprise.micro.5.3',
	'suse.linux.enterprise.micro.5.3-patch',
	'suse.linux.enterprise.micro.5.4',
	'suse.linux.enterprise.micro.5.4-patch',
	'suse.linux.enterprise.micro.5.5',
	'suse.linux.enterprise.micro.5.5-patch',
	'suse.linux.enterprise.micro.6.0',
	'suse.linux.enterprise.micro.6.0-patch',
	#'suse.manager.4.0',
	#'suse.manager.4.0-patch',
	#'suse.manager.4.1',
	#'suse.manager.4.1-patch',
	#'suse.manager.4.2',
	#'suse.manager.4.2-patch',
	'suse.manager.4.3',
	'suse.manager.4.3-patch',
	'suse.storage.7',
	'suse.storage.7-patch',
	#'suse.openstack.cloud.5',
	#'suse.openstack.cloud.5-patch',
	#'suse.openstack.cloud.5.12computenode-patch',
	#'suse.openstack.cloud.5.12computenode',
	#'suse.openstack.cloud.6',
	#'suse.openstack.cloud.6-patch',
	#'suse.openstack.cloud.7',
	#'suse.openstack.cloud.7-patch',
	'suse.openstack.cloud.8',
	'suse.openstack.cloud.8-patch',
	'suse.openstack.cloud.9',
	'suse.openstack.cloud.9-patch',
	'opensuse.leap.micro.5.2',
	'opensuse.leap.micro.5.2-patch',
	'opensuse.leap.micro.5.3',
	'opensuse.leap.micro.5.3-patch',
	'opensuse.leap.micro.5.4',
	'opensuse.leap.micro.5.4-patch',
	'opensuse.leap.micro.5.5',
	'opensuse.leap.micro.5.5-patch',
	'opensuse.leap.15.6',
	'opensuse.leap.15.6-patch',
	'opensuse.leap.15.5',
	'opensuse.leap.15.5-patch',
	#'opensuse.leap.15.4',
	#'opensuse.leap.15.4-patch',
	#'opensuse.leap.15.3',
	#'opensuse.leap.15.3-patch',
	#'opensuse.leap.15.2',
	#'opensuse.leap.15.2-patch',
	'opensuse.tumbleweed',	# patch does not make sense
);

########### configuration what to output in the index.html #######################


chdir("/mounts/mirror/SuSE/ftp.suse.com/pub/projects/security/oval/")||die;

my $ovalprefix ="oval:org.opensuse.security";

my $defserial = 1;

my %mypackages = ();
my %myversions = ();
my %mytests = ();

my %suppress = ();

my $objserial = 2009030400;
my $staserial = 2009030400;
my $tstserial = 2009030400;

my $ovalobj="";
my $ovalsta="";
my $ovaltst="";

my %ovalids = ();
my %allids = ();
my $dupid = 0;
sub read_ovalids {
	open(OVALIDS,"<$cverepobase/data/ovalids")||return;
	while (<OVALIDS>) {
		chomp;
		my $check;
		my $id;
		($check, $id) = split(/ /);

		if ($allids{$id}) {
			print STDERR "duplicate id $id!\n";
			$dupid = 1
		}
		$allids{$id} = 1;
		$ovalids{$check} = $id;
		die "wrong formatted id $id" unless ($id =~ /:([^:]*):(\d*)$/);
		if ($1 eq "obj") { if ($objserial<=$2) { $objserial = $2+1; } }
		if ($1 eq "tst") { if ($tstserial<=$2) { $tstserial = $2+1; } }
		if ($1 eq "ste") { if ($staserial<=$2) { $staserial = $2+1; } }
	}
	close(OVALIDS);
}
die "duplicate ids, see above" if $dupid;

sub write_ovalids {
	open(OVALIDS,">$cverepobase/data/ovalids")||die;
	foreach (sort keys %ovalids) {
		print OVALIDS "$_ $ovalids{$_}\n";
	}
	close(OVALIDS);
}

sub get_testid($) {
	my ($tst) = @_;
	if ($ovalids{$tst}) {
		$mytests{$tst} = $ovalids{$tst};
	} else {
		$mytests{$tst} = "$ovalprefix:tst:$tstserial";
		$tstserial++;
		$ovalids{$tst} = $mytests{$tst};
	}
	return $ovalids{$tst};
}

sub get_stateid($) {
	my ($ver) = @_;

	if ($ovalids{$ver}) {
		$myversions{$ver} = $ovalids{$ver};
	} else {
		$myversions{$ver} = "$ovalprefix:ste:$staserial";
		$staserial++;
		$ovalids{$ver} = $myversions{$ver};
	}
	return $ovalids{$ver};
}

sub get_objid($) {
	my ($pkg) = @_;

	if ($ovalids{$pkg}) {
		$mypackages{$pkg} = $ovalids{$pkg};
	} else {
		$mypackages{$pkg} = "$ovalprefix:obj:$objserial";
		$objserial++;
		$ovalids{$pkg} = $mypackages{$pkg};
	}
	return $ovalids{$pkg};
}

#rpm packagename
sub add_package($) {
	my($pkg) = @_;
	return $mypackages{$pkg} if (defined($mypackages{$pkg}));
	# new id.

	my $objid = get_objid($pkg);
	$ovalobj.=
	"	<rpminfo_object id=\"$objid\" version=\"1\" xmlns=\"http://oval.mitre.org/XMLSchema/oval-definitions-5#linux\">\n" .
	"		<name>$pkg</name>\n" .
	"	</rpminfo_object>\n";
	return $objid;
}

# ==version
# <version
# >=version
sub add_version($) {
	my($ver) = @_;
	return $myversions{$ver} if (defined($myversions{$ver}));
	# new id.

	my $stateid = get_stateid($ver);

	my ($newver,$arch) = split(/:/,$ver);

	$ovalsta .=
	"  <rpminfo_state id=\"$stateid\" version=\"1\" xmlns=\"http://oval.mitre.org/XMLSchema/oval-definitions-5#linux\">\n";
	if (defined($arch)) {
		$ovalsta .= "   <arch datatype=\"string\" operation=\"pattern match\">$arch</arch>\n";
	}
	if ($newver =~ /^==(.*)/) {
		my $version = $1;
		if ($version =~ /-/) {	# assume full version release
			$ovalsta .= "   <evr datatype=\"evr_string\" operation=\"equals\">0:$version</evr>\n";
		} else {
			$ovalsta .= "   <version operation=\"equals\">$version</version>\n";
		}
	}
	if ($newver =~ /^>([^=].*)/) {
		$ovalsta .= "   <evr datatype=\"evr_string\" operation=\"greater than\">0:$1-0</evr>\n";
	}
	if ($newver =~ /^>=(.*)/) {
		$ovalsta .= "   <evr datatype=\"evr_string\" operation=\"greater than or equal\">0:$1-0</evr>\n";
	}
	if ($newver =~ /^<(.*)/) {
		# 0 is the epoch, always 0 for suse
		$ovalsta .= "   <evr datatype=\"evr_string\" operation=\"less than\">0:$1</evr>\n";
	}
	$ovalsta .= "  </rpminfo_state>\n";
	return $stateid;
}

# Add GPG signature ste entry
sub add_signature($) {
	my($ver) = @_;
	return $myversions{$ver} if (defined($myversions{$ver}));
	# new id.

	my $stateid = get_stateid($ver);

	$ovalsta .=
	"  <rpminfo_state id=\"$stateid\" version=\"1\" xmlns=\"http://oval.mitre.org/XMLSchema/oval-definitions-5#linux\">\n";
	if ($ver =~ /^==(.*)/) {
		$ovalsta .= "   <signature_keyid operation=\"equals\">$1</signature_keyid>\n";
	}
	$ovalsta .= "  </rpminfo_state>\n";
	return $stateid;
}

sub emit_uname_test($) {
	my ($uname) = @_;
	my $origuname = $uname;

	$uname =~ s/^uname\(//;
	$uname =~ s/\)$//;

	print STDERR "emitting uname test for $uname\n";
	my $unameobj = $ovalids{"uname"};
	if (!defined($mypackages{"uname"})) {
		$unameobj = get_objid("uname");

        	$ovalobj .="	<unix-def:uname_object xmlns:unix-def=\"http://oval.mitre.org/XMLSchema/oval-definitions-5#unix\" id=\"$unameobj\" version=\"1\"/>\n";
	}

	my $unamestate = $ovalids{"uname-$uname"};
	if (!defined($myversions{"uname-$uname"})) {
		$unamestate = get_stateid("uname-$uname");
		$ovalsta .=
		"  <unix-def:uname_state xmlns:unix-def=\"http://oval.mitre.org/XMLSchema/oval-definitions-5#unix\" id=\"$unamestate\" version=\"1\">\n" .
		"   <unix-def:os_release operation=\"pattern match\">$uname</unix-def:os_release>\n" .
		"  </unix-def:uname_state>\n";
	}

	my $unametst = get_testid($origuname);

	$ovaltst .=
		"	<unix-def:uname_test xmlns:unix-def=\"http://oval.mitre.org/XMLSchema/oval-definitions-5#unix\" check=\"at least one\" comment=\"kernel version $uname is currently running\" id=\"$unametst\" version=\"1\">\n" .
		"		<unix-def:object object_ref=\"$unameobj\"/>\n" .
		"		<unix-def:state state_ref=\"$unamestate\"/>\n" .
		"	</unix-def:uname_test>\n";
	return $unametst;
}

sub emit_test($) {
	my ($tst) = @_;
	my $origtst = $tst;
	my $negation = 0;

	return $mytests{$origtst} if (defined($mytests{$origtst}));

	if ($tst =~ /uname\(/) {
		return emit_uname_test($tst);
	}
	if ($tst =~ /^!/) {
		$negation = 1;
		$tst =~ s/^!//;
	}

	if (	($tst =~ /^(.*)(==.*)$/)||
		($tst =~ /^(.*)(<.*)$/)	||
		($tst =~ /^(.*)(>.*)$/) ||
		($tst =~ /^(.*)(>=.*)$/)
	) {
		my $xpkg = $1;
		my $xver = $2;
		# print "$tst -> $xpkg relation $xver\n";
		my $pkg = add_package($xpkg);
		my $ver = add_version($xver);

		if ($xver =~ /:(.*)/) {
			$xver =~ s/:\(/ for /;
			$xver =~ s/\)//;
			$xver =~ s/\|/,/g;
		}

		my $tstid = get_testid($origtst);

		# for non-multiversions packages, any RPM < version shows affectedness
		# for kernel and KMP packages where there can be multiple installed versions
		# we check if "all" are < FIXEDVERSION and if yes, complain.
		# do not use it for ==
		my $match = "at least one";
		if ((($xpkg =~ /kernel-/) || ($xpkg =~ /-kmp-/)) && ($tst !~ /==/)) {
			$match = "all";
		}

		if ($negation) {
			$match = "none satisfy";
		}

		$ovaltst .=
		"	<rpminfo_test id=\"$tstid\" version=\"1\" comment=\"$xpkg is " . escape_xml($xver) . "\" check=\"$match\" xmlns=\"http://oval.mitre.org/XMLSchema/oval-definitions-5#linux\">\n".
		"		<object object_ref=\"$pkg\"/>\n".
		"		<state state_ref=\"$ver\"/>\n".
		"	</rpminfo_test>\n";
		return $tstid;
	}
	die "No negation, case tst = $tst unhandled.\n" if (!$negation);

	# Negation without condition
	my $xpkg = $tst;
	my $pkg = add_package($tst);

	my $tstid = get_testid($origtst);

	$ovaltst .=
	"	<rpminfo_test id=\"$tstid\" version=\"1\" comment=\"$xpkg is not installed\" check=\"all\" check_existence=\"none_exist\" xmlns=\"http://oval.mitre.org/XMLSchema/oval-definitions-5#linux\">\n".
	"		<object object_ref=\"$pkg\"/>\n".
	"	</rpminfo_test>\n";
	return $tstid;
}

# Add GPG signature test
sub emit_test_signature($) {
	my ($tst) = @_;

	return $mytests{$tst} if (defined($mytests{$tst}));

	if (	($tst =~ /^(.*)(==.*)$/) ) {
		my $xpkg = $1;
		my $xver = $2;
		# print "$tst -> $xpkg relation $xver\n";
		my $pkg = add_package($xpkg);
		my $ver = add_signature($xver);

		my $tstid = get_testid($tst);

		$ovaltst .=
		"	<rpminfo_test id=\"$tstid\" version=\"1\" comment=\"$xpkg is signed with openSUSE key\" check=\"all\" xmlns=\"http://oval.mitre.org/XMLSchema/oval-definitions-5#linux\">\n".
		"		<object object_ref=\"$pkg\"/>\n".
		"		<state state_ref=\"$ver\"/>\n".
		"	</rpminfo_test>\n";
		return $tstid;
	}
	die "$tst is bad syntax\n";
}

sub
getscore_string($$) {
        my ($cve,$source) = @_;

        &SMASHData::read_smash_issue($cve);

	my $cvssv3 = $SMASHData::cvssv3{$cve};
	my $cvssv4 = $SMASHData::cvssv4{$cve};

	my $str = "";

	my $scoremap;
	if (defined($cvssv3->{$source})) {
		$scoremap = $cvssv3->{$source};
		$str .= "cvss3=\"" . $scoremap->{'base_score'} . "/" . $scoremap->{'base_vector'} . "\" ";
	}
	if (defined($cvssv4->{$source})) {
		$scoremap = $cvssv4->{$source};
		$str .= "cvss4=\"" . $scoremap->{'base_score'} . "/" . $scoremap->{'base_vector'} . "\" ";
	}
	return $str;
}

sub
getseverity($$) {
        my ($cve,$source) = @_;

        &SMASHData::read_smash_issue($cve);
	my $cvssv3 = $SMASHData::cvssv3{$cve};
	return "" unless (defined($cvssv3));
	if (defined($cvssv3->{$source})) {
		my $scoremap = $cvssv3->{$source};
		my $score = $scoremap->{'base_score'};

		my $sev = "low";
		$sev = "medium"       if ($score >= 4.0);
		$sev = "high"         if ($score >= 7.0);
		$sev = "critical"     if ($score >= 9.0);
		return "impact=\"$sev\" ";
	}
	return "";
}

sub product_check($) {
	my ($product) = @_;
	my $pkg;
	my $release;

	if ($product =~ /openSUSE/)						{ $pkg = "openSUSE-release"; }
	if ($product =~ /HPE Helion OpenStack/i)				{ $pkg = "hpe-helion-openstack-release"; }
	if ($product =~ /Subscription Management Tool/)				{ $pkg = "sle-smt-release"; }
	if ($product =~ /SUSE Cloud Compute Node for SUSE Linux Enterprise 12/)	{ $pkg = "suse-sle12-cloud-compute-release"; }
	if ($product =~ /SUSE CaaS Platform/)					{ $pkg = "caasp-release"; }
	if ($product =~ /SUSE Enterprise Storage/)				{ $pkg = "ses-release"; }
	if ($product =~ /SUSE Linux Enterprise Storage/)			{ $pkg = "ses-release"; }
	if ($product =~ /SUSE Lifecycle Management Server/)			{ $pkg = "sle-slms-release"; }
	if ($product =~ /SUSE Linux Enterprise Build System Kit/)		{ $pkg = "sle-bsk-release"; }
	if ($product =~ /SUSE Linux Enterprise Desktop/) 			{ $pkg = "sled-release"; }
	if ($product =~ /SUSE Linux Enterprise Micro/) 				{ $pkg = "SUSE-MicroOS-release"; }
	if ($product =~ /SUSE Linux Micro/) 					{ $pkg = "SL-Micro-release"; }
	if ($product =~ /SUSE Linux Enterprise for SAP/)			{ $pkg = "SLES_SAP-release"; }
	if ($product =~ /SUSE Linux Enterprise High Availability GEO Extension/){ $pkg = "sle-ha-geo-release"; }
	if ($product =~ /SUSE Linux Enterprise High Availability Extension/)	{ $pkg = "sle-ha-release"; }
	if ($product =~ /SUSE Linux Enterprise High Performance Computing.*12/)	{ $pkg = "SLE-HPC-release"; }
	if ($product =~ /SUSE Linux Enterprise High Performance Computing/)	{ $pkg = "SLE_HPC-release"; }
	if ($product =~ /SUSE Linux Enterprise High Performance Computing.*15/)	{ $pkg = "SLE_HPC-release"; }
	if ($product =~ /SUSE Linux Enterprise High Performance Computing.*LTSS/){ $pkg = "SLE_HPC-LTSS-release"; }
	if ($product =~ /SUSE Linux Enterprise High Performance Computing.*ESPOS/){ $pkg = "SLE_HPC-ESPOS-release"; }
	if ($product =~ /SUSE Linux Enterprise Installer Updates/)		{ $pkg = "sle-module-basesystem-release"; }
	if ($product =~ /SUSE Linux Enterprise Live Patching/)			{ $pkg = "sle-live-patching-release"; }
	if ($product =~ /SUSE Linux Enterprise Live Patching/)		{ $pkg = "sle-module-live-patching-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Package Hub/)		{ $pkg = "sle-module-packagehub-subpackages-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Advanced Systems Management/)		{ $pkg = "sle-module-adv-systems-management-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Basesystem/)		{ $pkg = "sle-module-basesystem-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for CAP/)			{ $pkg = "sle-module-cap-tools-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Certifications/)	{ $pkg = "sle-module-certifications-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Containers/)		{ $pkg = "sle-module-containers-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Desktop Applications/){ $pkg = "sle-module-desktop-applications-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Development Tools/)	{ $pkg = "sle-module-development-tools-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for HPC/)			{ $pkg = "sle-module-hpc-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Legacy/)		{ $pkg = "sle-module-legacy-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Open Buildservice Development Tools/)	{ $pkg = "sle-module-development-tools-obs-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Public Cloud/)	{ $pkg = "sle-module-public-cloud-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Python 2/)		{ $pkg = "sle-module-python2-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Python 3/)		{ $pkg = "sle-module-python3-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Confidential Computing Technical Preview/)		{ $pkg = "sle-module-confidential-computing-release-"; }
	if ($product =~ /SUSE Real Time Module/)				{ $pkg = "sle-module-rt-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for SAP Applications/)	{ $pkg = "sle-module-sap-applications-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Server Applications/)	{ $pkg = "sle-module-server-applications-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for SUSE Manager Proxy/)	{ $pkg = "sle-module-suse-manager-proxy-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for SUSE Manager Server/)	{ $pkg = "sle-module-suse-manager-server-release"; }
	if ($product =~ /SUSE Manager Proxy Module/)				{ $pkg = "sle-module-suse-manager-proxy-release"; }
	if ($product =~ /SUSE Manager Server Module/)				{ $pkg = "sle-module-suse-manager-server-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Toolchain/)		{ $pkg = "sle-module-toolchain-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Transactional Server/){ $pkg = "sle-module-transactional-server-release"; }
	if ($product =~ /SUSE Linux Enterprise Module for Web and Scripting/)	{ $pkg = "sle-module-web-scripting-release"; }
	if ($product =~ /SUSE Linux Enterprise Point of Sale/)			{ $pkg = "sle-pos-release"; }
	if ($product =~ /SUSE Linux Enterprise Real Time Extension/)		{ $pkg = "SUSE-Linux-Enterprise-RT-release"; }
	if ($product =~ /SUSE Linux Enterprise Server 11-EXTRA/)		{ $pkg = "sles-release"; }
	if ($product =~ /^Container/)						{ $pkg = "sles-release"; }
	if ($product =~ /^Image/)						{ $pkg = "sles-release"; }
	if ($product =~ /SUSE Linux Enterprise Server/)				{ $pkg = "sles-release"; }
	if ($product =~ /SUSE Linux Enterprise Server for SAP/)			{ $pkg = "SLES_SAP-release"; }
	if ($product =~ /SUSE Linux Enterprise Server.*15.*LTSS/)		{ $pkg = "sles-ltss-release"; }
	if ($product =~ /SUSE Linux Enterprise Software Development Kit/)	{ $pkg = "sle-sdk-release"; }
	if ($product =~ /SUSE Linux Enterprise Workstation Extension/)		{ $pkg = "sle-we-release"; }
	if ($product =~ /SUSE Liberty Linux/)					{ $pkg = "sles_es-release-server"; }
	if ($product =~ /SUSE Linux Enterprise Real Time/)			{ $pkg = "SLE_RT-release"; }
	if ($product =~ /SUSE Manager/)						{ $pkg = "suse-manager-server-release"; }
	if ($product =~ /SUSE Manager Tools/)					{ $pkg = "sle-manager-tools-release"; }
	if ($product =~ /SUSE Manager Proxy/)					{ $pkg = "SUSE-Manager-Proxy-release"; }
	if ($product =~ /SUSE Manager Server/)					{ $pkg = "SUSE-Manager-Server-release"; }
	if ($product =~ /SUSE OpenStack Cloud Crowbar/)				{ $pkg = "suse-openstack-cloud-crowbar-release"; }
	if ($product =~ /SUSE OpenStack Cloud/)					{ $pkg = "suse-openstack-cloud-release"; }
	if ($product =~ /SUSE Package Hub/)					{ $pkg = "sles-release"; }
	if ($product =~ /SUSE Studio Onsite/)					{ $pkg = "sle-studioonsite-release"; }
	if ($product =~ /SUSE WebYast/)						{ $pkg = "sle-11-WebYaST-release"; }
	if ($product =~ /openSUSE Leap .* SLE Imports/)				{ $pkg = "openSUSE-release"; }
	if ($product =~ /openSUSE Tumbleweed/)					{ $pkg = "openSUSE-release"; }

	$product =~ s/ SLE Imports//;

	print STDERR "unknown product $product?\n" unless (defined($pkg));
	die "DIE: unknown product $product" unless (defined($pkg));

	$product =~ s/ NonFree//;

	print "$product\n" if ($product =~ /12.SP3*/);
	if ($product eq "SUSE Linux Enterprise Server 12 SP3-TERADATA") {
		return "uname(4.4.*TDC)";
	}
	if ($product eq "SUSE Linux Enterprise Server Teradata 12 SP3-LTSS") {
		return "SLES_LTSS_TERADATA-release==12.3";
	}
	if ($product eq "SUSE Linux Enterprise Server 15 SP4-TERADATA") {
		return "sles-teradata-release==15.4";
	}

	if (($product =~ / (\d*\.\d*)$/) || ($product =~ / (\d*)$/)) {
		$release = $1;
		if (($release =~ /12/) && (($pkg =~ /-module-/) || ( $pkg eq "sle-live-patching-release" ))) { # modules and live patching are for all sps!
			$release = ">=$release";
		} else {
			$release = "==$release";
		}
	}

	if ($product =~ /SUSE Linux Enterprise Micro (\d*.\d*)/) {
		$release = $1;
		if ($release eq "6.0") {
			$pkg = "SL-Micro-release";
		} else {
			$pkg = "SUSE-MicroOS-release";
		}
		$release = "==$release";
	}

	if ($product =~ /SUSE Liberty Linux (\d*)/)	{
		my $major = $1;

		if ($major == 9) {
			$pkg = "sll-release";
		} else {
			$pkg = "sles_es-release-server";
		}
		$release = ">=$major";
	}
	if ($product =~ /SUSE Linux Enterprise High Availability Extension (\d*)/)	{
		my $major = $1;
		if ($major == 11) {
			$pkg = "sle-hae-release";
		}
		# 12 and 15 are sle-ha-release
	}
	if ($product =~ /SUSE Linux Enterprise Live Patching (\d*)/)	{
		my $major = $1;
		if ($major == 12) {
			$pkg = "sle-live-patching-release";
		}
		if ($major == 15) {
			$pkg = "sle-module-live-patching-release";
		}
	}
	if ($product =~ /SUSE Linux Enterprise Real Time (\d*)/)	{
		my $major = $1;
		if ($major == 12) {
			$pkg = "SUSE-Linux-Enterprise-RT-release";
		}
		if ($major == 15) {
			$pkg = "SLE_RT-release";
		}
	}


	# On SLE11 it is SUSE_SLES_SAP-release
	if ($product =~ /SUSE Linux Enterprise.*SAP/ && defined($release) && ($release =~ /11/)) {
		$pkg = "SUSE_SLES_SAP-release";
	}

	# SLE12 BCL has sles-bcl-release, SLE15 BCL has SLES_BCL-release
	# BCL is always on on non-GA SPs.
	if ($product =~ /SUSE Linux Enterprise Server (\d*) SP(\d*)-BCL/) {
		my $major = $1;
		if ($major == 12) {
			$pkg = "sles-bcl-release";
		}
		if ($major == 15) {
			$pkg = "SLES_BCL-release";
		}
	}

	# public cloud hacks
	if ($product =~ /^Image SLES(\d*)/) { $release = "==$1"; }
	if ($product =~ /^Image SLES(\d*)-SP(\d*)/) { $release = "==$1.$2"; }

	# container hacks
	# casp 4.0 is sles15 sp1 based
	if ($product =~ /^Container caasp\/v4\//) { $release = "==15.1"; }
	if ($product =~ /^Image caasp4\//) { $release = "==15.1"; }
	# casp 4.5 is sles 15 sp2 based
	if ($product =~ /^Container caasp\/4.5\//) { $release = "==15.2"; }
	if ($product =~ /^Image caasp45\//) { $release = "==15.2"; }
	# ses6 is 15 sp1 based
	if ($product =~ /^Image ses6\//) { $release = "==15.2"; }
	# ses7 is 15 sp1 based
	if ($product =~ /^Image ses7\//) { $release = "==15.2"; }

	if ($product =~ /(\d*) SP(\d)/) { $release = "==$1.$2"; }
	if ($product =~ /(\d*)-LTSS/) { $release = "==$1"; }
	if ($product =~ /(\d*)-ESPOS/) { $release = "==$1"; }
	if ($product =~ /(\d*) SP(\d*)-LTSS/) { $release = "==$1.$2"; }
	if ($product =~ /11-SECURITY$/) { $release = ">=11.3"; }
	if ($product =~ /11-PUBCLOUD$/) { $release = ">=11.3"; }
	if ($product =~ /11-EXTRA$/) { $release = ">=11"; }
	if ($product =~ /Tumbleweed/) { $release = ">=20210101"; }

	print STDERR "unknown release detection for $product\n" unless (defined($release));
	die "DIE: unknown release detection for $product" unless (defined($release));

	#print STDERR "mapped $product to $pkg==$release\n";
	return "$pkg$release";
}

sub escape_xml($) {
	my ($x) = @_;

	$x =~ tr/\x{80}-\x{ff}/?/;	# everything weird ... go away

	$x =~ s/&/&amp;/g;
	$x =~ s/</&lt;/g;
	$x =~ s/>/&gt;/g;
	$x;
}

# Novell template subroutines
sub add_novell_html_body_start1 {
    my $f = shift;
	print $f '<!--#include virtual="/common/inc/hdr.html"-->'."\n";
	print $f $cgi->start_div({-id => "tier4"})."\n";
	print $f $cgi->start_a({-name => "top"}).$cgi->end_a()."\n";
	print $f $cgi->start_div({-id => "contentcontainer"})."\n";
	print $f $cgi->start_div({-id => "content"})."\n";
	print $f $cgi->start_div({-id => "contenthead"})."\n";
	print $f $cgi->div({-id => "breadcrumb"},'&gt;',
	    $cgi->a({-href => "/"},"support"))."\n";
}

sub add_novell_html_body_start2 {
    my $g = shift;
	print $g $cgi->hr()."\n";
	print $g $cgi->end_div()."\n";
	print $g $cgi->start_div({-id => "mainbody"})."\n";
	print $g $cgi->script({-type => "text/javascript"},
    	'var showRater = false; var showSendToFriend = false;')."\n";
}

sub add_novell_html_end {
    my $h = shift;
	print $h $cgi->end_div()."\n";
	print $h $cgi->end_div()."\n";
	print $h $cgi->start_div({-id => "subnav"});
	print $h '<!--#include virtual="/inc/nav/support.html"-->';
	print $h $cgi->end_div()."\n";
	print $h $cgi->end_div()."\n";
	print $h $cgi->end_div()."\n";
	print $h '<!--#include virtual="/common/inc/ftr.html"-->'."\n";
}
#End Novell template subroutines

sub generate_oval($$$) {
	my ($productmatch,$vulnfn,$patchfn) = @_;

	my @products = ();
	foreach my $product (sort keys %UpdateInfoReader::patches) {
		next unless ($product =~ /$productmatch/);
		next if ($product =~ /:/);	# codestreams in the list
		push @products, $product;
	}
	# products for which we want to dump oval

	print STDERR "generating for: " . join("\n",@products) ;# if (-t STDERR);

	# write out header
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
	my $curtime = POSIX::strftime("%Y-%m-%dT%H:%M:%S",$sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst );

	$ovalobj="";
	$ovalsta="";
	$ovaltst="";
	%mypackages = ();
	%mytests = ();
	%myversions = ();

	open(OVAL,">$patchfn.new")||die "failed to open $patchfn for writing: $!";
	binmode(OVAL,":utf8");
	print OVAL <<EOF;
<?xml version="1.0" encoding="UTF-8"?>
	<oval_definitions
		xsi:schemaLocation="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux linux-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#unix unix-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-common-5 oval-common-schema.xsd"
		xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:oval="http://oval.mitre.org/XMLSchema/oval-common-5"
		xmlns:oval-def="http://oval.mitre.org/XMLSchema/oval-definitions-5">
	  <generator>
	      <oval:product_name>Marcus Updateinfo to OVAL Converter</oval:product_name>
	      <oval:schema_version>5.5</oval:schema_version>
	      <oval:timestamp>$curtime</oval:timestamp>
	  </generator>
EOF

	my %allpatches;

	# 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) {
			# This has led to many constant RED runs, and also to undue pressure from e.g. platform1.
			# Disabled in QA patches on request of cert team.
			next if ($UpdateInfoReader::patchinqa{$patch});

			# code below is currently unused.
			if ($UpdateInfoReader::patchinqa{$patch}) {
				my $isembargoed = 0;
				print STDERR "$patch is in QA\n" if (-t STDERR);
				my %refs = %{$UpdateInfoReader::patchreferences{$patch}};

				foreach my $ref (sort keys %refs) {
					if ($ref =~ /CVE/) {
						# if we posted an advisory or a note, its public.
						next if (defined($CanDBReader::advisoryids{$ref}));
						next if (defined($CanDBReader::note{$ref}));
						# otherwise check for embargoed state and skop
						if (	$CanDBReader::embargoed{$ref} ||
							$SMASHData::embargoedcves{$ref}
						) {
							print STDERR "$patch has embargoed CVE $ref\n";
							$isembargoed = 1;
						}
					}
				}
				next if ($isembargoed);
			}
			$allpatches{$patch} = 1;
		}
	}

	print OVAL "<definitions>\n";

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

		if ($UpdateInfoReader::patchtype{$patch} ne "security") {
			# check if we have CVEs in references.
			next unless (grep (/CVE/,keys %{$UpdateInfoReader::patchreferences{$patch}}));
		}

		#print "scanning patch $patch\n";

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

			push @affectedproducts,$product;
			push @allprods,$product;

			my @crita = ();

			# x-release == version AND ( package1=version1 OR package2=version2 )

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

			my %packagearchs = ();
			if (defined($UpdateInfoReader::patchpackagearchs{$patch})) {
				%packagearchs = %{$UpdateInfoReader::patchpackagearchs{$patch}};
			}

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

			# print STDERR "$product -> " . join (",",contained($product)) . "\n";
			push @affectedproducts,contained($product);
			push @allprods,contained($product);

			my %allprods = map { $_ => 1 } @allprods;
			@allprods = sort keys %allprods;

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

				my $ver = $packages{$pkg};
				my @arch = sort keys %{$packagearchs{$pkg}};
				my $arch = "";

				if (@arch) {
					$arch = ":(" . join("|",@arch) . ")";
				}

				my $xx  = $pkg . "<" . $ver . $arch;
				my $xx2 = $pkg . "-" . $ver;

				# We are a livepatch, but not the initial livepatch with version 1.
				if (	(($pkg =~ /kernel-livepatch/) || ($pkg =~ /kgraft-patch/)) &&
					($ver !~ /^1-/)
				) {
					# emit:
					# AND(	(uname(os-release) == version-release-default is running),
					# 	kernel-default == version-release is installed,
					# 	OR(	(this livepatch is not installed),
					# 		livepatchversion < fixedversion is installed
					# 	)
					#)

					my $kdefver = $pkg;
					$kdefver =~ s/-default//;
					$kdefver =~ s/kernel-livepatch-//;
					$kdefver =~ s/kgraft-patch-//;
					$kdefver =~ s/_/./g;
					# we miss the final buildcount, reverse engineer that
					# FIXME also miss architectures, but does not matter that much
					my @defvers = grep (/^$kdefver/,sort (keys %UpdateInfoReader::allkerneldefaultversions));
					my $defver = $defvers[$[];
					print STDERR "patch mode: lp $pkg in product $product: $kdefver found: " . join(",",@defvers) . "\n";

					# new. just emit 1 entry to avoid an OR() block. weird indentation to keep it in the flow.
					push @crita, 	"<criteria operator=\"AND\">\n" .
							"\t\t\t\t<criterion test_ref=\"" . emit_test("uname($kdefver-default)") . "\" comment=\"kernel-default $kdefver is running\"/>\n" .
							"\t\t\t\t<criteria operator=\"OR\">\n" .
							"\t\t\t\t\t<criterion test_ref=\"" . emit_test("!$pkg") . "\" comment=\"$pkg is not installed\"/>\n" .
							"\t\t\t\t\t<criterion test_ref=\"" . emit_test($xx) . "\" comment=\"$xx2 is installed\"/>\n" .
							"\t\t\t\t</criteria>\n" .
							"\t\t\t</criteria>\n";
				} else {
					#print "\tpackage $pkg, version=$packages{$pkg}\n";
					push @crita, "<criterion test_ref=\"" . emit_test($xx) . "\" comment=\"$xx2 is installed\"/>";
					#print "\t test is $tst\n";
				}
			}
			my $prodcrita;

			if (!@crita) {
				print STDERR "empty package list on $patch in $product?\n";
				next;
			}

			$prodcrita  = "\t<criteria operator=\"AND\">\n";
			if ($#allprods > 0) {	# also all the base products that consume this product
				$prodcrita  .= "\t\t<criteria operator=\"OR\">\n";
				foreach my $xprod (sort @allprods) {
					$prodcrita .= "\t\t\t<criterion test_ref=\"" . emit_test(product_check($xprod)) . "\" comment=\"$xprod is installed\"/>\n";
				}
				$prodcrita .= "\t\t</criteria>\n";
			} else {
				$prodcrita .= "\t\t<criterion test_ref=\"" . emit_test(product_check($product)) . "\" comment=\"$product is installed\"/>\n";
			}
			if ($#crita > 0) {
				$prodcrita .= "\t\t<criteria operator=\"OR\">\n";
			}
			$prodcrita .= "\t\t\t" . join("\n\t\t\t",@crita). "\n";
			if ($#crita > 0) {
				$prodcrita .= "\t\t</criteria>\n";
			}
			$prodcrita .= "\t</criteria>\n";
			push @productcrita,$prodcrita;
		}
		if (!@productcrita) {
			print STDERR "No product criteria listed for $patch, skipping.\n";
			next;
		}

		# unify and sort
		my %affectedproducts = map { $_ => 1 } @affectedproducts;
		@affectedproducts = sort keys %affectedproducts;


		print OVAL "<definition id=\"$ovalprefix:def:$defserial\" version=\"1\" class=\"patch\">\n";
		$defserial++;	# FIXME: make this also a unique identifier
		print OVAL "\t<metadata>\n";
		my $title = $UpdateInfoReader::patchtitle{$patch};
		if (!defined($title)) {
			warn "no patchtitle in $patch\n";
			$title = "";
		}
		print OVAL "\t\t<title>" . escape_xml($title);
		if (defined($UpdateInfoReader::patchseverity{$patch})) {
			print OVAL " (\u$UpdateInfoReader::patchseverity{$patch})";
		}
		print OVAL " (in QA)" if ($UpdateInfoReader::patchinqa{$patch});
		print OVAL "</title>\n";

		print OVAL "\t\t<affected family=\"unix\">\n";
		foreach my $product (sort @affectedproducts) {
			print OVAL "\t\t\t<platform>" . $product . "</platform>\n";
		}

		#$platforms{$upd->{'release'}->{'value'}} = 1;
		print OVAL "\t\t</affected>\n";

		# References:

		# print OVAL \t\t<reference ref_id=\"xxxx\" ref_url=\"url\" source=\"SOURCE\"/>
		foreach my $cve (sort keys %{$UpdateInfoReader::patchreferences{$patch}}) {
			next if ($suppress{$cve});
			if ($cve =~ /CVE-/) {
				print OVAL "\t\t<reference ref_id=\"$cve\" ref_url=\"https://www.suse.com/security/cve/$cve/\" source=\"CVE\"/>\n";
			}
			if ($cve =~ /^\d*$/) {
				print OVAL "\t\t<reference ref_id=\"$cve\" ref_url=\"https://bugzilla.suse.com/$cve\" source=\"BUGZILLA\"/>\n";
			}
		}
		if (defined($CanDBReader::patch2susenotice{$patch})) {
			my $notice = $CanDBReader::patch2susenotice{$patch};
			print OVAL "\t\t<reference ref_id=\"$notice\" ref_url=\"$CanDBReader::advisoryid2url{$notice}\" source=\"SUSE-SU\"/>\n";
		}

		print OVAL "\t\t<description>\n";
		my $desc = $UpdateInfoReader::patchdescription{$patch};
		if (!defined($desc)) {
			warn "No patch description in $patch, products: " . join(",",@affectedproducts) . "\n";
			$desc = "";
		}
		print OVAL escape_xml($desc);

		print OVAL "\nThis patch is currently in QA and not yet available for download.\n" if ($UpdateInfoReader::patchinqa{$patch});

		print OVAL "\t\t</description>\n";

		print OVAL "<advisory from=\"security\@suse.de\">\n";
			($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($UpdateInfoReader::patchissued{$patch});
			my $issuedtime = POSIX::strftime("%Y-%m-%d",$sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst );

			print OVAL "\t<issued date=\"$issuedtime\"/>\n";
			print OVAL "\t<updated date=\"$issuedtime\"/>\n";
		if (defined($UpdateInfoReader::patchseverity{$patch})) {
			print OVAL "\t<severity>\u$UpdateInfoReader::patchseverity{$patch}</severity>\n";
		}
#     <cve cvss3="5.4/CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N" cwe="CWE-285" href="https://access.redhat.com/security/cve/CVE-2019-0816" impact="moderate" public="20190305">CVE-2019-0816</cve>
		foreach my $cve (sort keys %{$UpdateInfoReader::patchreferences{$patch}}) {
			next if ($suppress{$cve});
			if ($cve =~ /CVE-/) {
				&SMASHData::read_smash_issue($cve,0);
				my $cvss = getscore_string($cve,"SUSE");
				my $sev = getseverity($cve,"SUSE");
				print OVAL "\t<cve ${sev}${cvss}href=\"https://www.suse.com/security/cve/$cve/\">$cve at SUSE</cve>\n";
				$cvss = getscore_string($cve,"National Vulnerability Database");
				$sev = getseverity($cve,"National Vulnerability Database");
				print OVAL "\t<cve ${sev}${cvss}href=\"https://nvd.nist.gov/vuln/detail/$cve\">$cve at NVD</cve>\n";
			}
			if ($cve =~ /^\d*$/) {
				print OVAL "\t<bugzilla href=\"https://bugzilla.suse.com/$cve\">SUSE bug $cve</bugzilla>\n";
			}
		}
		my %cpes = ();
		foreach my $product (sort @affectedproducts) {
			my $cpe = &SMASHData::get_cpe($product);
			if (defined($cpe)) {
				$cpes{$cpe} = 1;
			} else {
				warn "no cpe found for $product in $patch\n";
			}
		}
		if (%cpes) {
			print OVAL "\t<affected_cpe_list>\n";
			foreach my $cpe (sort keys %cpes) {
					print OVAL "\t\t<cpe>$cpe</cpe>\n";
			}
			print OVAL "\t</affected_cpe_list>\n";
		}

		print OVAL "</advisory>\n";
#
#<advisory from="secalert@redhat.com">
#        <severity>Moderate</severity>
#        <rights>Copyright 2015 Red Hat, Inc.</rights>
#        <issued date="2015-06-29"/>
#        <updated date="2015-06-29"/>
#        <cve href="https://access.redhat.com/security/cve/CVE-2015-0252">CVE-2015-0252</cve>
#        <bugzilla href="https://bugzilla.redhat.com/1199103" id="1199103">CVE-2015-0252 xerces-c: crashes on malformed input</bugzilla>
#    <affected_cpe_list>
#            <cpe>cpe:/o:redhat:enterprise_linux:7</cpe>
#    </affected_cpe_list>
#</advisory>

		print OVAL "\t</metadata>\n";

		if ($#productcrita > 0) {
			print OVAL "\t<criteria operator=\"OR\">\n";
			print OVAL join("", @productcrita);
			print OVAL "\t</criteria>\n";
		} else {
			print OVAL join("", @productcrita);
		}

		print OVAL "</definition>\n";
	}
	print OVAL "</definitions>\n";

	print OVAL "<tests>\n";
	print OVAL $ovaltst;
	print OVAL "</tests>\n";
	print OVAL "<objects>\n";
	print OVAL $ovalobj;
	print OVAL "</objects>\n";
	print OVAL "<states>\n";
	print OVAL $ovalsta;
	print OVAL "</states>\n";

	print OVAL "</oval_definitions>\n";

	close(OVAL) || die "writing out oval failed: $!";

	$ovalobj="";
	$ovalsta="";
	$ovaltst="";
	%mypackages = ();
	%mytests = ();
	%myversions = ();

	if (!system("oscap oval validate $patchfn.new")) {
		# we put in date stamps currently.
		rename("$patchfn.new",$patchfn);
		unlink("$patchfn.new");
		system("gzip --keep --force $patchfn");
		system("bzip2 --keep --force $patchfn");
	} else {
		die "$patchfn.new failed to validate";
	}

	# flush the id cache ... as we currently largely do not run to the end, keep some sanity.
	write_ovalids();

	#
	# Now vulnerabilities with and without affectedness
	#
	foreach my $mode ("","-affected") {
		$vulnfn =~ s/.xml/$mode.xml/;

		open(OVAL,">$vulnfn.new")||die "failed to open $vulnfn for writing: $!";
		binmode(OVAL,":utf8");
		print OVAL <<EOF;
<?xml version="1.0" encoding="UTF-8"?>
	<oval_definitions
		xsi:schemaLocation="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux linux-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#unix unix-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-common-5 oval-common-schema.xsd"
		xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:oval="http://oval.mitre.org/XMLSchema/oval-common-5"
		xmlns:oval-def="http://oval.mitre.org/XMLSchema/oval-definitions-5">
	  <generator>
	      <oval:product_name>Marcus Updateinfo to OVAL Converter</oval:product_name>
	      <oval:schema_version>5.5</oval:schema_version>
	      <oval:timestamp>$curtime</oval:timestamp>
	  </generator>
EOF

		print OVAL "<definitions>\n";

		my %allcves = ();

		foreach my $cve (keys %UpdateInfoReader::products) { $allcves{$cve} = 1; }
		foreach my $cve (keys %SMASHData::pkgstate) {
			# check for embargoed ... but if we posted an advisory or a note, its public.
			next if (($CanDBReader::embargoed{$cve} || $SMASHData::embargoedcves{$cve}) && !defined($CanDBReader::advisoryids{$cve}) && !defined($CanDBReader::note{$cve}));

			$allcves{$cve} = 1;
		}

		foreach my $cve (sort keys %allcves) {
			next if ($suppress{$cve});

			my $serial = $cve;
			next unless ($cve =~ /CVE-\d*-\d*/);
			$serial =~ s/CVE-//;
			$serial =~ s/-//;

			my %platforms = ();

			my %cpes = ();

			# Filter products of this cve against the ones which we want
			my %products = ();
			if (defined($UpdateInfoReader::products{$cve})) {
				%products = %{$UpdateInfoReader::products{$cve}};
			}
			my %smashprods = ();
			if (defined ($SMASHData::pkgstate{$cve})) {
				%smashprods= %{$SMASHData::pkgstate{$cve}};
			}

			# print "$cve has " . join("\n",keys %products) . "\n";

			my %affectedproducts = (); # include base products, for platform and cpe list

			#
			# Kernel Live Patch handling..
			#
			my %spcriteria = ();
			my %kernellpmap = (); # {sp -> { kernel-default version -> { kernellivepatch -> version } }}

			my $criteria = "";
			my @lpproducts = grep(/Live.Patching/,keys %products);
			if (@lpproducts) {
				# note 2 cases ... initial livepatch, or real livepatch

				foreach my $product (sort @lpproducts) {
					my $sp = $product;
					$sp =~ s/.*([1-9][0-9](.SP[1-9])?)/$1/;

					if (!defined($products{$product}) || !defined($products{$product}->{"packages"})) {
						print STDERR "no packages in $product for $cve?\n";
						next;
					}

					$sp = "SUSE Linux Enterprise Module for Basesystem $sp" if ($sp =~ /15/);
					$sp = "SUSE Linux Enterprise Server $sp" if ($sp =~ /12/);

					# print STDERR "$product mapped to SP $sp\n";

					my %packages = %{$products{$product}->{"packages"}};
					# print STDERR "$product:\n";
					# print STDERR Dumper(\%packages);

					# 'kernel-livepatch-5_3_18-150300_59_60-default' => '7-150300.2.2',
					# 'kernel-livepatch-5_3_18-150300_59_49-default' => '9-150300.2.2',
					# 'kernel-livepatch-5_3_18-150300_59_68-default' => '3-150300.2.2',
					# 'kernel-livepatch-5_3_18-150300_59_71-default' => '2-150300.2.2'

					# print STDERR "all default kernel versions: " . join(",",sort keys %UpdateInfoReader::allkerneldefaultversions) . "\n";

					my $lowestver = "99-99";
					my @lpcriteria = ();
					foreach my $pkg (sort keys %packages) {
						my $flavor;
						next unless (($pkg =~ /kernel-livepatch-[0-9]/) || ($pkg =~ /kgraft-patch-[0-9]/));

						next if ($pkg =~ /xen/);	# FIXME: currently do not do kernel-xen livepatches

						next if ($packages{$pkg} =~ /^1-/);	# skip always existing initial livepatch, as it has no fixes.

						my $kdefver = $pkg;
						if ($kdefver =~ /-default/) {
							$kdefver =~ s/-default//;
							$flavor = "default";
						}
						if ($kdefver =~ /-rt/) {
							$kdefver =~ s/-rt//;
							$flavor = "rt";
						}
						$kdefver =~ s/kernel-livepatch-//;
						$kdefver =~ s/kgraft-patch-//;
						$kdefver =~ s/_/./g;
						# we miss the final buildcount, reverse engineer that
						# FIXME also miss architectures, but does not matter that much
						my @defvers = grep (/^$kdefver/,sort (keys %UpdateInfoReader::allkerneldefaultversions));
						my $defver = $defvers[$[];
						if (defined($defver)) {
							# print STDERR "product $product: $kdefver found: " . join(",",@defvers) . "\n";

							$kernellpmap{$sp}->{$defver}->{$pkg}=$packages{$pkg};

							# OR(
							#	< lowest kernel,
							# 	AND(kernel1, NONE(livepatch1>=fixed version1 installed)),
							#)
							if (&UpdateInfoReader::versioncompare($lowestver,$defver)) {
								$lowestver = $defver;
							}

							push @lpcriteria,
							"\t\t\t\t\t<criteria operator=\"AND\">\n" .
							"\t\t\t\t\t\t<criterion test_ref=\"" . emit_test("kernel-$flavor==$defver") . "\" comment=\"kernel-$flavor $defver is installed\"/>\n" .
							"\t\t\t\t\t\t<criterion test_ref=\"" . emit_test("!$pkg>=$packages{$pkg}") . "\" comment=\"no $pkg is greater or equal than $packages{$pkg}\"/>\n" .
							"\t\t\t\t\t</criteria>\n";
						} else {
							print STDERR "ERROR: product/pkg $product/$pkg: $kdefver found no issues: " . join(",",@defvers) . "\n" if -t STDERR;
						}
					}
					if (@lpcriteria) {
						# emit a comment for third party users / scan engines
						my $criteria = "<!-- For kernel default affectedness we evaluate affectedness for the lowest kernel without livepatch for the issue, and for each livepatched kernel version we check if no livepatch is installed with the right version or higher\n".
								"OR(\n".
								"	AND(affected kernel1, NONE SATISFY(livepatch for kernel1 >= fixed livepatch1 version installed)),\n".
								"	AND(affected kernel2, NONE SATISFY(livepatch for kernel2 >= fixed livepatch2 version installed)),\n".
								"	kernel <= version before first livepatch provided\n".
								") -->\n";
						$criteria .= "\t\t\t\t<criteria operator=\"OR\">\n";
						$criteria .= join("",@lpcriteria);
						$criteria .= "\t\t\t\t\t<criterion test_ref=\"" . emit_test("kernel-default<$lowestver") . "\" comment=\"kernel-default is less than $lowestver\"/>\n";
						$criteria .= "\t\t\t\t</criteria>\n";
						# print STDERR $criteria;
						# print STDERR Dumper(\%kernellpmap);

						$spcriteria{$sp} = $criteria;
						foreach my $contprod (contained($sp)) {
							$spcriteria{$contprod} = $criteria;
						}
					}
				}
			}

			# This is the selected OVAL product list.
			foreach my $product (@products) {
				# updateinfo ...

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

				if (defined($products{$product})) {
					$platforms{$product} = 1;
					$affectedproducts{$product} = 1;
					my $cpe = &SMASHData::get_cpe($product);
					if (defined($cpe)) {
						$cpes{$cpe} = 1;
					} else {
						warn "no cpe found for $product in CVE $cve\n";
					}
				}

				# smash ...
				if (defined($smashprods{$product})) {
					my %pkgs = %{$smashprods{$product}};
					foreach my $pkg (keys %pkgs) {
						# NOTE: if you update the conditions of this list, adjust it also some 100 lines below in the emitter code.
						if (($pkgs{$pkg} eq "Not affected")		||
						    (($mode eq "-affected")	&&	(
								($pkgs{$pkg} eq "Affected")	||
						    		($pkgs{$pkg} eq "In progress")
							)
						    )
						) {
							# FIXME: select CPE here too? Or only for AFFECTED?
							print STDERR "$cve - $product - $pkg - $pkgs{$pkg}\n" if -t STDERR;
							$platforms{$product} = 1;
							$affectedproducts{$product} = 1;
							my $cpe = &SMASHData::get_cpe($product);
							if (defined($cpe)) {
								$cpes{$cpe} = 1;
							} else {
								warn "no cpe found for $product in CVE $cve\n";
							}
							last;
						}
					}
				}
			}

			unless (%platforms) {
				# print "no platform found for $cve\n";
				next;
			}
			foreach my $product (keys %platforms) {
				$affectedproducts{$product} = 1;

				if (contained($product)) {
					foreach my $contprod (contained($product)) {
						$affectedproducts{$contprod} = 1;
						my $cpe = &SMASHData::get_cpe($contprod);
						if (defined($cpe)) {
							$cpes{$cpe} = 1;
						} else {
							warn "no cpe found for $contprod in CVE $cve\n";
						}
					}
				}
			}

			print OVAL "<definition id=\"$ovalprefix:def:$serial\" version=\"1\" class=\"vulnerability\">\n";
			print OVAL " <metadata>\n";
			print OVAL " <title>$cve</title>\n";
			print OVAL "    <affected family=\"unix\">\n";
			foreach my $mname (sort keys %affectedproducts) {
				print OVAL "            <platform>$mname</platform>\n";
			}
			print OVAL "    </affected>\n";
			# only reference should be to the CVE id, no others.
			print OVAL "    <reference ref_id=\"Mitre $cve\" ref_url=\"https://cve.mitre.org/cgi-bin/cvename.cgi?name=$cve\" source=\"CVE\"/>\n";
			print OVAL "    <reference ref_id=\"SUSE $cve\" ref_url=\"https://www.suse.com/security/cve/$cve\" source=\"SUSE CVE\"/>\n";

			# put in all advisory ids that reference this CVE
			if (defined($CanDBReader::advisoryids{$cve})) {
				my %notices = map { $_ => 1 } split (/,/,$CanDBReader::advisoryids{$cve});

				foreach my $notice (sort keys %notices) {
					if (defined($CanDBReader::advisoryid2url{$notice}))  {
						print OVAL "\t\t<reference ref_id=\"$notice\" ref_url=\"$CanDBReader::advisoryid2url{$notice}\" source=\"SUSE-SU\"/>\n";
					}
				}
			}

			print OVAL "    <description>\n";
			my $desc = get_description($cve);
			if (defined($desc)) {
				print OVAL "    " . escape_xml($desc) . "\n";
			} else {
				print OVAL "    Unknown.\n";
			}
			print OVAL "    </description>\n";


			&SMASHData::read_smash_issue($cve,0);
			print OVAL "<advisory from=\"security\@suse.de\">\n";
			if (defined($SMASHData::severity{$cve})) {
				print OVAL "\t<severity>\u$SMASHData::severity{$cve}</severity>\n";
			}
			my $sev = getseverity($cve,"SUSE");
			my $cvss = getscore_string($cve,"SUSE");
			print OVAL "\t<cve ${sev}${cvss}href=\"https://www.suse.com/security/cve/$cve/\">$cve at SUSE</cve>\n";
			$sev = getseverity($cve,"National Vulnerability Database");
			$cvss = getscore_string($cve,"National Vulnerability Database");
			print OVAL "\t<cve ${sev}${cvss}href=\"https://nvd.nist.gov/vuln/detail/$cve\">$cve at NVD</cve>\n";


			my %xx = ();
			if ($CanDBReader::bugzillas{$cve}) {
				%xx  = map { $_ => 1 } split(/,/,$CanDBReader::bugzillas{$cve});
			}
			foreach my $bug (sort keys %xx) {
				print OVAL "\t<bugzilla href=\"https://bugzilla.suse.com/$bug\">SUSE bug $bug</bugzilla>\n";
			}
			if (%cpes) {
				print OVAL "\t<affected_cpe_list>\n";
				foreach my $cpe (sort keys %cpes) {
					print OVAL "\t\t<cpe>$cpe</cpe>\n";
				}
				print OVAL "\t</affected_cpe_list>\n";
			}
			print OVAL "</advisory>\n";


			print OVAL " </metadata>\n";

			# Criteria emitter
			# we declare affectedness by:
			# OR	(AND(OR (PRODUCTLIST), OR(PACKAGE IS INSTALLED)),AND(OR(PRODUCTLIST2),OR(PACKAGE IS INSTALLED2)))

			my %productcrita = ();
			foreach my $product (sort keys %platforms) {
				my @crita = ();

				my %pkgannounced = ();

				if (defined($UpdateInfoReader::products{$cve}) &&
				    defined($UpdateInfoReader::products{$cve}->{$product})
				) {
					my %packages = ();

					if (defined($UpdateInfoReader::products{$cve}->{$product}->{'packages'})) {
						%packages = %{$UpdateInfoReader::products{$cve}->{$product}->{'packages'}};
					} else {
						print STDERR "$cve no packages for $product?\n";
						next;
					}
					my %packagearchs = ();
					if (defined($UpdateInfoReader::products{$cve}->{$product}->{'packagearchs'})) {
						%packagearchs = %{$UpdateInfoReader::products{$cve}->{$product}->{'packagearchs'}};
					}

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

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

						$pkgannounced{$pkg} = 1;

						my @archs = sort keys %{$packagearchs{$pkg}};
						my $arch = "";
						if (@archs) {
							$arch = ":(" . join("|",@archs) . ")";
						}

						my $ver = $packages{$pkg};
						# strip of the buildcounter. bsc#1094432
						# we occassionaly might have different buildcounters across platforms (although this is a bug)
						# no logner needed as we now have the different architecture method.
						# $ver =~ s/\.(\d*)$//;

						my $xx  = $pkg . "<" . $ver . $arch;
						my $xx2 = $pkg . "-" . $ver;

						# Testing GPG signature validation for openSUSE Leap
						if ($product =~ /openSUSE Leap/) {
							my $xx3 = $pkg . "==" . "b88b2fd43dbdc284";
							push(@crita, "<criteria operator=\"AND\">\n" .
								"	<criterion test_ref=\"" . emit_test($xx) . "\" comment=\"$xx2 is installed\"/>\n" .
								"	<criterion test_ref=\"" . emit_test_signature($xx3) . "\" comment=\"$pkg is signed with openSUSE key\"/>\n" .
								"</criteria>" );
						} else {
							if (($pkg eq "kernel-default") && defined($spcriteria{$product})) {
								push @crita, $spcriteria{$product};
								$injectedlivepatch = 1;
							} else {
								push(@crita, "<criterion test_ref=\"" . emit_test($xx) . "\" comment=\"$xx2 is installed\"/>");
							}
						}
					}
					if (!$injectedlivepatch && defined($spcriteria{$product})) {
						push @crita, $spcriteria{$product};
					}
				}
				if (defined($smashprods{$product})) {
					my $emit = 0;
					my %pkgs = %{$smashprods{$product}};
					foreach my $pkg (sort keys %pkgs) {
						next if ($pkgs{$pkg} eq "Released");			# Released -> tracked already

						my @rpms = ($pkg);
						if (defined($UpdateInfoReader::product2packages{$product}->{$pkg})) {
							@rpms = sort keys %{$UpdateInfoReader::product2packages{$product}->{$pkg}};
						}

						# TODO: In progress -> hmm ... in qa handling not yet done TODO

						if ($pkgs{$pkg} eq "Not affected") {
							print STDERR "Not affected: $cve - $product - $pkg - $pkgs{$pkg}\n" if -t STDERR;

							foreach my $rpm (@rpms) {
								next if ($pkgannounced{$rpm});				# Already in above released section, for bad smash data
								my $xx = "$rpm==0";	# just emit a "FALSE" $rpm version == 0 check for not affected
								push @crita, "<criterion test_ref=\"" . emit_test($xx) . "\" comment=\"$rpm is not affected\"/>";
							}
						}

						if ($mode eq "-affected") {
							if (($pkgs{$pkg} eq "Affected") || ($pkgs{$pkg} eq "In progress")) {
								print STDERR "Affected: $cve - $product - $pkg - $pkgs{$pkg}\n" if -t STDERR;
								foreach my $rpm (@rpms) {
									next if ($pkgannounced{$rpm});				# Already in above released section, for bad smash data
									my $xx = "$rpm>0";	# just emit a "TRUE" $rpm version > 0 check for affected
									push @crita, "<criterion test_ref=\"" . emit_test($xx) . "\" comment=\"$rpm is affected\"/>";
								}
							}
						}
					}
				}
				my $packcrita = "";
				if ($#crita>0) {
					$packcrita .= "\t\t\t<criteria operator=\"OR\">\n";
				}
				$packcrita .= "\t\t\t\t" . join("\n\t\t\t\t",@crita). "\n";
				if ($#crita>0) {
					$packcrita .= "\t\t\t</criteria>\n";
				}
				if (!defined($productcrita{$packcrita})) {
					$productcrita{$packcrita}  = $product;
				} else {
					$productcrita{$packcrita} .= ",$product";
				}
			}

			if (keys %productcrita > 1) {
				print OVAL "\t<criteria operator=\"OR\">\n";
			}
			foreach my $crita (sort keys %productcrita) {
				my @products = ();

				die "crita in $cve not contains criteria: $crita" unless ($crita =~ /criterion/);;


				foreach my $xprod (split(/,/,$productcrita{$crita})) {
					push @products,$xprod,contained($xprod);
				}

				my %products = map { $_ => 1 } @products;
				@products = keys %products;

				print OVAL "\t\t<criteria operator=\"AND\">\n";
				# print product matchers...
				# if more than 1 OR the conditions
				my $prodindent = "";
				if ($#products > 0) {
					print OVAL "\t\t\t<criteria operator=\"OR\">\n";
					$prodindent="\t";
				}
				foreach my $product (sort @products) {
					print OVAL "$prodindent\t\t\t<criterion test_ref=\"" . emit_test(product_check($product)) . "\" comment=\"$product is installed\"/>\n";
				}
				if ($#products > 0) {
					print OVAL "\t\t\t</criteria>\n";
				}
				# print the actual criteria

				print OVAL $crita;

				print OVAL "\t\t</criteria>\n";
			}
			if (keys %productcrita > 1) {
				print OVAL "\t</criteria>\n";
			}

			print OVAL "</definition>\n";
		}

		print OVAL "</definitions>\n";

		print OVAL "<tests>\n";
		print OVAL $ovaltst;
		print OVAL "</tests>\n";
		print OVAL "<objects>\n";
		print OVAL $ovalobj;
		print OVAL "</objects>\n";
		print OVAL "<states>\n";
		print OVAL $ovalsta;
		print OVAL "</states>\n";

		print OVAL "</oval_definitions>\n";

		close(OVAL)||die "writing out oval failed: $!";

		# we put in date stamps currently.

		my $line = "";
		my $def = 0;
		my $indef = 0;
		my %olddefs = ();
		my %newdefs = ();
		my %issued = ();
		my %updated = ();
		if (open(OVALOLD,"<$vulnfn")) {	# might be new
			while ($line = <OVALOLD>) {
				if ($line =~ /<\/definition>/) {
					$indef = 0;
					$olddefs{$def} .= $line;
					die "no updated in $def?" if (!defined($updated{$def}));
					die "no issued in $def?" if (!defined($issued{$def}));
					next;
				}
				if ($indef) {
					# filter out the issued and updated tag for compare.
					if ($line =~ /<issued date="([^"]*)"/) {
						$issued{$def} = $1;
						next;
					}
					if ($line =~ /<updated date="([^"]*)"/) {
						$updated{$def} = $1;
						next;
					}
					$olddefs{$def} .= $line;
					next;
				}
				if ($line =~ /<definition id="([^"]*)"/) {
					$indef = 1;
					$def = $1;
					$olddefs{$def} = $line;
					next;
				}
			}
			close(OVALOLD);
		}
		close(OVALOLD);
		open(OVALNEWNEW,">$vulnfn.new.new");
		open(OVALNEW,"<$vulnfn.new");
		($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
		my $issuedtime = POSIX::strftime("%Y-%m-%d",$sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst );

		#print OVAL "\t<issued date=\"$issuedtime\"/>\n";
		#print OVAL "\t<updated date=\"$issuedtime\"/>\n";
		while ($line = <OVALNEW>) {
			if ($line =~ /<\/definition>/) {
				$indef = 0;
				$newdefs{$def} .= $line;

				if (!defined($olddefs{$def})) {
					print STDERR "NEW: $def\n";
					# inject current issued and updated tag.
					$newdefs{$def} =~ s/(<advisory[^>]*>)/$1\n\t<issued date=\"$issuedtime\"\/>\n\t<updated date=\"$issuedtime\"\/>/;
					print OVALNEWNEW $newdefs{$def};
					$newdefs{$def} = "";
					next;
				}
				if (!defined($issued{$def})) { $issued{$def} = $issuedtime; print STDER "issued was not defined?\n"; }
				if (!defined($updated{$def})) { $updated{$def} = $issuedtime; print STDER "updated was not defined?\n"; }

				if ($olddefs{$def} eq $newdefs{$def}) {
					$newdefs{$def} =~ s/(<advisory[^>].*>)/$1\n\t<issued date=\"$issued{$def}\"\/>\n\t<updated date=\"$updated{$def}\"\/>/;
					# print STDERR "UNCHANGED: $def\n";
				} else {
					print STDERR "CHANGED: $def\n";
					# open(OLDDEFS,">olddef"); print OLDDEFS $olddefs{$def}; close(OLDDEFS);
					# open(NEWDEFS,">newdef"); print NEWDEFS $newdefs{$def}; close(NEWDEFS);
					# system("diff -u olddef newdef"); unlink("olddef");unlink("newdef");
					$newdefs{$def} =~ s/(<advisory[^>].*>)/$1\n\t<issued date=\"$issued{$def}\"\/>\n\t<updated date=\"$issuedtime\"\/>/;
				}
				print OVALNEWNEW $newdefs{$def};
				$newdefs{$def} = "";
				next;
			}
			if ($indef) {
				$newdefs{$def} .= $line;
				# store, do not print
				next;
			}
			if ($line =~ /<definition id="([^"]*)"/) {
				$indef = 1;
				$def = $1;
				#print STDERR "processsing definition $def\n";
				$newdefs{$def} = $line;
				# store, do not print
				next;
			}
			print OVALNEWNEW $line;
		}
		close(OVALNEW);
		close(OVALNEWNEW);
		if (!system("oscap oval validate $vulnfn.new.new")) {
			rename("$vulnfn.new.new",$vulnfn);
			unlink("$vulnfn.new");
			system("gzip --keep --force $vulnfn");
			system("bzip2 --keep --force $vulnfn");
		} else {
			die "$vulnfn.new.new did not validate.";
		}
	}
	# flush the id cache ... as we currently largely do not run to the end, keep some sanity.
	write_ovalids();
}

umask 022;

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


&read_ovalids();

&SMASHData::read_all_cached_issues();

# print STDERR Dumper(\%SMASHData::prod2cpe) if -t STDERR;


print "All products:" . join("\n",sort keys %UpdateInfoReader::patches);

generate_oval("SUSE Liberty Linux 7","suse.liberty.linux.7.xml","suse.liberty.linux.7-patch.xml");
generate_oval("SUSE Liberty Linux 8","suse.liberty.linux.8.xml","suse.liberty.linux.8-patch.xml");
generate_oval("SUSE Liberty Linux 9","suse.liberty.linux.9.xml","suse.liberty.linux.9-patch.xml");

# EOL generate_oval("openSUSE Leap 15.4","opensuse.leap.15.4.xml","opensuse.leap.15.4-patch.xml");
generate_oval("openSUSE Leap 15.5","opensuse.leap.15.5.xml","opensuse.leap.15.5-patch.xml");
generate_oval("openSUSE Leap 15.6","opensuse.leap.15.6.xml","opensuse.leap.15.6-patch.xml");
generate_oval("SUSE Linux Enterprise.*15.SP3","suse.linux.enterprise.15-sp3.xml","suse.linux.enterprise.15-sp3-patch.xml");
generate_oval("SUSE Linux Enterprise.*15.SP4","suse.linux.enterprise.15-sp4.xml","suse.linux.enterprise.15-sp4-patch.xml");
generate_oval("SUSE Linux Enterprise.*15.SP5","suse.linux.enterprise.15-sp5.xml","suse.linux.enterprise.15-sp5-patch.xml");
generate_oval("SUSE Linux Enterprise.*15.SP6","suse.linux.enterprise.15-sp6.xml","suse.linux.enterprise.15-sp6-patch.xml");
generate_oval("SUSE Linux Enterprise.*12.SP5","suse.linux.enterprise.12-sp5.xml","suse.linux.enterprise.12-sp5-patch.xml");

generate_oval("(Manager.*4.3|SUSE Linux Enterprise.*15 SP4)","suse.manager.4.3.xml","suse.manager.4.3-patch.xml");
generate_oval("SUSE Linux Enterprise.*15","suse.linux.enterprise.15.xml","suse.linux.enterprise.15-patch.xml");
generate_oval("SUSE Linux Enterprise.*12","suse.linux.enterprise.12.xml","suse.linux.enterprise.12-patch.xml");
generate_oval("SUSE Linux Enterprise Micro 5.0","suse.linux.enterprise.micro.5.0.xml","suse.linux.enterprise.micro.5.0-patch.xml");
generate_oval("SUSE Linux Enterprise Micro 5.1","suse.linux.enterprise.micro.5.1.xml","suse.linux.enterprise.micro.5.1-patch.xml");
generate_oval("SUSE Linux Enterprise Micro 5.2","suse.linux.enterprise.micro.5.2.xml","suse.linux.enterprise.micro.5.2-patch.xml");
generate_oval("SUSE Linux Enterprise Micro 5.3","suse.linux.enterprise.micro.5.3.xml","suse.linux.enterprise.micro.5.3-patch.xml");
generate_oval("SUSE Linux Enterprise Micro 5.4","suse.linux.enterprise.micro.5.4.xml","suse.linux.enterprise.micro.5.4-patch.xml");
generate_oval("SUSE Linux Enterprise Micro 5.5","suse.linux.enterprise.micro.5.5.xml","suse.linux.enterprise.micro.5.5-patch.xml");
generate_oval("SUSE Linux Enterprise Micro 5","suse.linux.enterprise.micro.5.xml","suse.linux.enterprise.micro.5-patch.xml");
generate_oval("SUSE Linux Enterprise Micro 6.0","suse.linux.enterprise.micro.6.0.xml","suse.linux.enterprise.micro.6.0-patch.xml");
# EOL generate_oval("openSUSE Leap 15.3","opensuse.leap.15.3.xml","opensuse.leap.15.3-patch.xml");
generate_oval("openSUSE Leap Micro 5.3","opensuse.leap.micro.5.3.xml","opensuse.leap.micro.5.3-patch.xml");
generate_oval("openSUSE Leap Micro 5.4","opensuse.leap.micro.5.4.xml","opensuse.leap.micro.5.4-patch.xml");
generate_oval("openSUSE Leap Micro 5.5","opensuse.leap.micro.5.5.xml","opensuse.leap.micro.5.5-patch.xml");
# EOL generate_oval("SUSE Linux Enterprise.*11","suse.linux.enterprise.11.xml","suse.linux.enterprise.11-patch.xml");
# EOL generate_oval("SUSE Linux Enterprise (Server|SDK|HAE).*11","suse.linux.enterprise.server.11.xml","suse.linux.enterprise.server.11-patch.xml");
generate_oval("SUSE Linux Enterprise (Server|Workstation|Module|SDK|HAE|Live|Real).*12","suse.linux.enterprise.server.12.xml","suse.linux.enterprise.server.12-patch.xml");
generate_oval("SUSE Linux Enterprise (Server|Workstation|Module|HAE|HPC|Live|Real).*15","suse.linux.enterprise.server.15.xml","suse.linux.enterprise.server.15-patch.xml");
generate_oval("SUSE Linux Enterprise (Desktop|.*Basesystem|.*Desktop.Applications|.*Development.Tools|.*Python|.*Package.Hub|.*Cloud Applications).*15","suse.linux.enterprise.desktop.15.xml","suse.linux.enterprise.desktop.15-patch.xml");
# EOL generate_oval("SUSE Linux Enterprise (Desktop|SDK).*12","suse.linux.enterprise.desktop.12.xml","suse.linux.enterprise.desktop.12-patch.xml");
# EOL generate_oval("(Cloud 7|SUSE Linux Enterprise Server 12 SP2)","suse.openstack.cloud.7.xml","suse.openstack.cloud.7-patch.xml");
# EOL generate_oval("(Cloud 8|Crowbar 8|SUSE Linux Enterprise Server 12 SP3)","suse.openstack.cloud.8.xml","suse.openstack.cloud.8-patch.xml");
# EOL generate_oval("(Cloud 9|Crowbar 9|SUSE Linux Enterprise Server 12 SP4)","suse.openstack.cloud.9.xml","suse.openstack.cloud.9-patch.xml");

# fixme, they only have some modules of SLE
# EOL generate_oval("(Manager.*4.0|SUSE Linux Enterprise.*15 SP1)","suse.manager.4.0.xml","suse.manager.4.0-patch.xml");
# EOL generate_oval("(Manager.*4.1|SUSE Linux Enterprise.*15 SP2)","suse.manager.4.1.xml","suse.manager.4.1-patch.xml");
# EOL generate_oval("(Manager.*4.2|SUSE Linux Enterprise.*15 SP3)","suse.manager.4.2.xml","suse.manager.4.2-patch.xml");
# EOL generate_oval("(Storage 6|SUSE Linux Enterprise.*15 SP1)","suse.storage.6.xml","suse.storage.6-patch.xml");
generate_oval("(Storage 7|SUSE Linux Enterprise.*15 SP[23])","suse.storage.7.xml","suse.storage.7-patch.xml");
# EOL generate_oval("(SUSE CaaS Platform 4.0|SUSE Linux Enterprise.*15 SP1)","suse.caasp.4.0.xml","suse.caasp.4.0-patch.xml");
# EOL generate_oval("openSUSE Leap 15.2","opensuse.leap.15.2.xml","opensuse.leap.15.2-patch.xml");
generate_oval("openSUSE Tumbleweed","opensuse.tumbleweed.xml","opensuse.tumbleweed-patch.xml");
generate_oval("SUSE Linux Enterprise.*15.SP2","suse.linux.enterprise.15-sp2.xml","suse.linux.enterprise.15-sp2-patch.xml");
# EOL generate_oval("SUSE Linux Enterprise.*15.SP1","suse.linux.enterprise.15-sp1.xml","suse.linux.enterprise.15-sp1-patch.xml");
# EOL generate_oval("SUSE Linux Enterprise.*12.SP4","suse.linux.enterprise.12-sp4.xml","suse.linux.enterprise.12-sp4-patch.xml");

# this takes 2 hours and wastes lots of disk space ... is too large to be of any use i think
#generate_oval(".*","full.xml","full-patch.xml");

write_ovalids();

open(OVALINDEX,">oldindex.html.new")||die "$!";
print OVALINDEX $cgi->start_html(
        -head => [ '<!--#include virtual="/common/inc/hdr_head_tier4.html"-->' ],
        -title => "SUSE Linux Enterprise OVAL Information",
        -author => 'security@suse.de',
        -meta => { 'Robots' => "INDEX, FOLLOW",
                   'keywords' => "oval, cve, security, suse linux" }
);
add_novell_html_body_start1(\*OVALINDEX);
print OVALINDEX $cgi->h1("OVAL Information")."\n";
print OVALINDEX $cgi->h2("Open Vulnerability and Assessment Language")."\n";
print OVALINDEX $cgi->p({-class => "ext-lnk"},
        $cgi->a({-href => "http://oval.mitre.org/"},
        "Find more about OVAL at MITRE"))."\n";
add_novell_html_body_start2(\*OVALINDEX);
print OVALINDEX $cgi->p("OVAL Information can be generated in various forms of data sets, including OVAL information by patch, by CVE ids, or by products.");
print OVALINDEX $cgi->p("For SUSE Linux products the information is currently generated by product only, further presentations are planned.");
print OVALINDEX $cgi->hr("By product OVAL Information:")."\n";
my $listxmls = "";
foreach my $product (@productlist) {
	my $size = -s "$product.xml";
	# FIXME: size,
	$listxmls .= $cgi->li($productnames{$product} . " - " . $cgi->a({-href => "$product.xml"},"$product.xml") . sprintf(" (%d MB)",($size/1024/1024))) . "\n";
}
print OVALINDEX $cgi->ul($listxmls);

print OVALINDEX "SUSE OVAL data is supplied under <a href=\"LICENSE\">Creative Commons license, with Attribution (CC-BY-4.0)</a>.\n";

add_novell_html_end(\*OVALINDEX);
print OVALINDEX $cgi->end_html();
close(OVALINDEX)||die;

system("sha256sum *xml > SHA256SUMs");

my $fn = "oldindex.html";
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 STDERR "SYNCING to Provo\n" if (-t STDERR);
#system("rsync --rsh 'ssh -i /suse/meissner/.ssh/id_nts' -av . nts\@vanik.provo.novell.com:/sync/support/docroot/security/oval/");
#system("ssh -i /suse/meissner/.ssh/id_nts nts\@vanik.provo.novell.com './sync_supportsecurity_oval.sh'");
print "SUCCESS\n";
