#!/usr/bin/perl

###############################################################################
##
## Functions for downloading and parsing Common Vulnerability DB data
##
###############################################################################

use strict;
use warnings;

use LWP::UserAgent;
use Time::ParseDate; # libtime-modules-perl
use POSIX qw(mktime);
use HTML::Parser;

## Get details of given CVE entry from NIST DB
sub fetchCVE {
	my $cve_id = shift;
	my $cve_base_url = shift;
	my $cvss_base_url = shift;
	my $url;

	trace "  Fetching $cve_id\n";

	$cve_id =~ s/^CAN/CVE/;

	##
	## get CVE Scores from NVD
	##
	$url =	$cvss_base_url . $cve_id;
	my $cvss = get $url;
	#die "  Unable to fetch CVE $cve_id" unless defined $cve;
	unless (defined $cvss) {
		error "Failed to download CVE: $url\n";
		#return ""; # Don't die, we might still get proper date info from the CVE at MITRE..
		$cvss = "";
	}

	# Check for error pages: referenced but unpublished CVEs :-/
	if ($cvss =~ /is\ valid\ CVE\ format,\ but\ CVE\ was\ not\ found/) {
		error "  $cve_id does not exist in NIST DB\n";
		#return ""; # Don't die, we might still get proper date info from the CVE at MITRE..
	}

	##
	## get CVE from MITRE
	##
	$url =	$cve_base_url . $cve_id;
	my $cve = get $url;
	unless (defined $cve) {
		error "Failed to download CVE: $url\n";
		return "";
	}

	# Check for error pages: referenced but unpublished CVEs :-/
	if ($cvss =~ /Could\ not\ find\ a\ CVE\ entry\ or\ candidate\ named/) {
		error "  $cve_id does not exist in MITRE DB\n";
		return "";
	}

	return (join ("",$cvss, $cve));
}


## Get CVE severity rating and report date and return
## (date base-score impact-score exploit-score)
sub parseCVE($$) {
	my $cve_id = shift;
	my $cve = shift;
	# use worst-case info as defaults
	my $cve_date = parsedate("today");
	my $cve_base = 10;
	my $cve_impact = 10;
	my $cve_exploit = 10;

	if ($cve eq "") {
		# No details means we assume worst-case (highest score, recent bug)
		if ($cve_id =~ /LOCAL-(.*)/) {
			note "  Assuming worst-case ratings for LOCAL CVE $cve_id..";
			$cve_date = parsedate("$1", NO_RELATIVE => 1, VALIDATE =>1);
		} else {
			error "  Got bad data for CVE/CVSS $cve_id, using worst-case defaults.."; 
		}
		return ($cve_date, $cve_base, $cve_impact, $cve_exploit);
	}
 
	# Reporting Date -> $cve_date
	unless ($cve =~ /<td colspan="2">Assigned:?\s+\((\d{4})(\d{2})(\d{2})\)<\/td>/ ||
					$cve =~ /<div class="smaller">CVE Version:\s+(\d{4})(\d{2})(\d{2})<\/div>/ ||
					$cve =~ /<td colspan="3" class="note">Candidate assigned on\s+(\d{4})(\d{2})(\d{2})/) {
		error "\n\n$cve\nUnable to extract date from above CVE, assume worst-case (today)\n";
	} else {
		$cve_date = parsedate("$2/$3/$1", NO_RELATIVE => 1, VALIDATE =>1);
	}

	# Base Score -> $cve_base
	unless ($cve =~ /CVSS\sv2\sBase\sScore:<\/span><a\shref=".*>(.*)<\/a>\s\(\w+\)/) {
		error "  Unable to parse CVSSv2 Base Score for $cve_id, assume worst-case\n";
	} else {
		$cve_base = $1;
	}

	# Impact Subscore -> $cve_impact
	unless ($cve =~ /<div\ class="row"><span\ class="label">Impact\ Subscore:<\/span>\s+(.*)<\/div>/) {
		error "  Unable to parse CVSSv2 Impact Score for $cve_id, assume worst-case\n";
	} else {
		$cve_impact = $1;
	}

	# Exploitability Subscore -> $cve_exploit
	unless ($cve =~ /<div\ class="row"><span\ class="label">Exploitability\ Subscore:<\/span>\s+(.*)<\/div>/) {
		error "  Unable to parse CVSSv2 Exploitability Score for $cve_id, assume worst-case\n";
	} else {
		$cve_exploit = $1;
	}
	
	#print "$cve_base\n";
	#print "$cve_impact\n";
	#print "$cve_exploit\n";
	return ($cve_date, $cve_base, $cve_impact, $cve_exploit);
}


1;
