#!/usr/bin/perl

# Purpose:
# 1. Take a Nessus output file (*.nbe) as input and generate a port list spreadsheet (*.csv) with the following
# column labels: Host Name, Port, Service Detected.
# 2. Take a Nessus output file (*.nbe) as input and generate a vulnerability list spreadsheet (*.csv) with the
# following column labels: Vulnerability, CVE, Risk, False Positive.
# Created: 2008-10-20
# Date Last Modified: 2008-11-03

use strict;
use Getopt::Std;

my %opts;

getopts('i:o:hs', \%opts);

my ($inputFile, $outputFile, $ignoreMe, $status, $fileLength);

if (-f $opts{i} && $opts{o} && !$opts{h}) {
	$inputFile = $opts{i};
	$outputFile = $opts{o};
	if ($opts{s}) {
		$status = 1;
	}
} else {
	print STDERR "./NessusPBE.pl -i <input .nbe> -o <output prefix>\n";
	exit;
}

# Ignore traceroute information
$ignoreMe = '(For\syour\sinformation,\shere\sis\sthe\straceroute';
# Ignore resolving (for now)
$ignoreMe .= '|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\sresolves\sas\S+)';

if ($status == 1) {
	open(INPUT, "< $inputFile") || die "Could not open file for input\n";
	for ($fileLength = 0; <INPUT>; $fileLength++) {}
	close INPUT;
}

# Open the Nessus file for reading
open(INPUT, $inputFile) || die "Could not open file for input\n";

# Hash of all open ports
# Key = IP Address
# Value = Port->Service Name
my (%OpenPorts, %UnknownPorts);
# Array of hashes of all open ports where Nessus couldn't enumerate the running service
#my (%UnknownPorts);
my (@tmpA, $file);

# Arrays for vulnerabilities
####### Each entry in the array is a String with the following format:
####### IP Address|Synopsis|Description|Solution|Risk Factor|CVSS Base Factor|Plugin Output|CVE|BID|Other Reference
####### Use the parse_vertical_bar subroutine to obtain an array of the values
# a[0][0] = Synopsis
# a[0][1] = Description
# a[0][2] = Solution
# a[0][3] = Risk Factor
# a[0][4] = CVSS Base Score
# a[0][5] = Plugin Output
# a[0][6] = CVE
# a[0][7] = BID
# a[0][8] = Other References
my (@none, @low, @medium, @high, @all);

my $statusCount;

# Read in each line from the Nessus input file.
while ($file = <INPUT>){
	# Show progress status
	if ($status == 1) {
		my $perc = ($./$fileLength) * 100;
		$perc =~ s/\.\d+//g;
		print "\b\b\b\b%$perc";
	}

	# Ignore these things 
	next if ($file =~ /$ignoreMe/);

	chomp($file);

	my @field = parse_vertical_bar($file);
	
	# Parse out the lines that are not results
	# This should parse out the Timestamps
	if ($field[0] eq 'results'){

		# Handle Port Scan Results
		if (!($field[3] eq 'general/tcp') && !($field[3] eq 'general/udp') && !($field[3] eq 'general/icmp')){

			# Parse the element in index 3 to extract service name, 
			# port number, and protocol
			# $element_3[0] = Service Name
			# $element_3[1] = Port Number
			# $element_3[2] = Protocol (tcp or udp)
			my @element_3 = parse_port_element($field[3]);

			my $fixedfield;
			($fixedfield = $field[2]) =~ s/\./_/g;
			# Hash of hashes
			# Hash is OpenPorts
			# OpenPorts -> IP Address -> Port Number -> Service Name
			# UnknownPorts -> IP Address -> Port Number -> Unknown
			if ($element_3[0] eq 'unknown'){
				$UnknownPorts{$fixedfield}{$element_3[1] . "/" . $element_3[2]} = $element_3[0];
			}
			else {
				$OpenPorts{$fixedfield}{$element_3[1] . "/" . $element_3[2]} = $element_3[0];
			}
		
		}

		# If there is a vulnerability and not just an identified open port
		# Add the array of strings
		if ($field[6] ne ""){

			# parsed6 contains:
			# 0: IP Address
			# 1: Port Number
			# 2: Synopsis
			# 3: Description
			# 4: Solution
			# 5: Risk factor
			# 6: CVSS Base factor
			# 7: Plugin Output
			# 8: CVE
			# 9: BID
			# 10: Other references
			# 11: Nessus Plugin ID
			my @parsed6 = parse_paragraph_test($field[2],$field[3],$field[6]);
			$parsed6[11] = $field[4];
			# Insert the port
			my @cleaned6 = cleanup(@parsed6);
			if ($parsed6[5] =~ m/none/i){
				push @none, [@cleaned6];
			}
			elsif ($parsed6[5] =~ m/low/i){
				push @low, [@cleaned6];
			}
			elsif ($parsed6[5] =~ m/medium/i){
				push @medium, [@cleaned6];
			}
			elsif ($parsed6[5] =~ m/high/i){
				push @high, [@cleaned6];
			}
			push @all, [@cleaned6];
		}

	} # Ends big if statement that parses out non-results

} # Ends:  while ($file = <INPUT>)

# Close the input file
close(INPUT);

# Assign Services
for my $i (0..$#all){

	if ($all[$i][7] =~ m/server is running on this port/i){
		my @pList = parse_port_element($all[$i][1]);
		# Replace existing
		$OpenPorts{$all[$i][0]}{$pList[1] . "/" . $pList[2]} = $pList[0];

		# Also need to implement taking out of 'unknown ports if it is there
		delete($UnknownPorts{$all[$i][0]}{$pList[1] . "/" . $pList[2]});
	}
}


# Print out the hash function of host names, open ports, and running services
open(OPENPORTLIST, ">>" . $outputFile . "-OpenPorts.csv") || die "Could not open $outputFile-OpenPorts.csv for output\n";
print OPENPORTLIST "Host Name,Port,Service Detected\n";
my (@openList, $tmpA);
for my $k1 (sort keys %OpenPorts){
	for my $k2 (sort keys %{$OpenPorts{$k1}}) {
		$tmpA = $k1 . "," . $k2 . "," . $OpenPorts{$k1}{$k2};
		push @openList, $tmpA;
	}
}

# IPs get sorted with spreadsheet program
#my @sortedKnownServices = sortIPs(@openList);
foreach my $lineService (@openList){
	$lineService =~ s/_/\./g;
	print OPENPORTLIST "$lineService\n";
}

close(OPENPORTLIST);


# Print out the hash of host names and open ports whose service is unknown
open(OPENUNKNOWNLIST, ">>" . $outputFile . "-UnknownPorts.csv") || die "Could not open $outputFile-UnknownPorts.csv for output\n";
print OPENUNKNOWNLIST "Host Name,Port\n";

my (@openList2, $tmpA2, $UnknownPorts);
for my $k3 (sort keys %$UnknownPorts){
	for my $k4 (sort keys %{$UnknownPorts->{$k3}}) {
		$tmpA2 = $k3 . "," . $k4; # . "," . $UnknownPorts->{$k3}{$k4};
		push @openList2, $tmpA2;
	}
}


# IPs get sorted with spreadsheet program
#my @sortedUnknownServices = sortIPs(@openList2);
foreach my $lineService2 (@openList2){
	$lineService2 =~ s/_/\./g;
	print OPENUNKNOWNLIST "$lineService2\n";
}

close(OPENUNKNOWNLIST);

# Print out the vulnerabilities
open(OPENVULNFILE, ">>" . $outputFile . "-VulnList.tsv") || die "Could not open $outputFile-VulnList.tsv for output\n";
			# 0: IP Address
			# 1: Port Number
			# 2: Synopsis
			# 3: Description
			# 4: Solution
			# 5: Risk factor
			# 6: CVSS Base factor
			# 7: Plugin Output
			# 8: CVE
			# 8: BID
			# 10: Other references
			# 11: Nessus Plugin ID
print OPENVULNFILE "Host Name\tPort Number\tSynopsis\tDescription\tSolution\tRisk factor\tCVSS Base Score\tPlugin Output\tCVE\tBID\tOther References\tNessus Plugin ID\tFalse Positive\n";

# Convert the array of arrays of strings into an array of strings
# seperated by the vertical bar
my (@allList, $tmpAll, $a1, $j1);
for $a1 (0..$#all){
	for $j1 (0..10){
		if ($all[$a1][$j1] ne ""){
			$tmpAll = $tmpAll . $all[$a1][$j1] . "|";
		}
		else{
			$tmpAll = $tmpAll . " " . "|";
		}
	}
	$tmpAll = $tmpAll . $all[$a1][11];
	push @allList, $tmpAll;
	$tmpAll = "";
}

my @sortedAll = sortIPAndRisk(@allList);

for my $i (0..$#sortedAll){
	my @toPrint = parse_vertical_bar($sortedAll[$i]);
	for my $j (0..$#toPrint-1){
		print OPENVULNFILE "$toPrint[$j]\t";
	}
	print OPENVULNFILE "$toPrint[$#toPrint]\n";
}
close(OPENVULNFILE);




sub cleanup{
	my @toClean = @_;

	for my $c (0..$#toClean){
		$toClean[$c] =~ s/^\s+//;
		$toClean[$c] =~ s/\r//g;
		$toClean[$c] =~ s/\n//g;
		$toClean[$c] =~ s/^\\n|\\n$//g;
		$toClean[$c] =~ s/^\\n|\\n$//g;
		$toClean[$c] =~ s/\\n/ /g;
	}
	return @toClean;
}


# Parses out the paragraph in the 7th field of the nbe item
# Expected Input:
# 1: IP Address String
# 2: The paragraph (7th field of the nbe item)
# Returns an array of the items in the paragraph:
# 0: IP Address
# 1: Port Number
# 2: Synopsis
# 3: Description
# 4: Solution
# 5: Risk factor
# 6: CVSS Base factor
# 7: Plugin Output
# 8: CVE
# 9: BID
# 10: Other references
sub parse_paragraph_test{
	my $ipAddressTest = shift;
	my $port = shift;
	my $paragraphTest = shift;
	my (@listOfItemsTest, @splitLineTest, @splitLineTest2);
	$listOfItemsTest[0] = $ipAddressTest;
	$listOfItemsTest[1] = $port;

	# Parse out the Synopsis verbiage, if any
	if ($paragraphTest =~ m/Synopsis[\s]*:/i){
		@splitLineTest = split(/Synopsis[\s]*:/, $paragraphTest);

		if ($paragraphTest =~ m/Description[\s]*:/i){
			@splitLineTest2 = split(/Description[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[2] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Solution[\s]*:/i){
			@splitLineTest2 = split(/Solution[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[2] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Risk factor[\s]*:/i){
			@splitLineTest2 = split(/Risk factor[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[2] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/CVSS Base Score[\s]*:/i){
			@splitLineTest2 = split(/CVSS Base Score[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[2] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Plugin output[\s]*:/i){
			@splitLineTest2 = split(/Plugin output[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[2] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/CVE[\s]*:/i){
			@splitLineTest2 = split(/CVE[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[2] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/BID[\s]*:/i){
			@splitLineTest2 = split(/BID[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[2] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Other references[\s]*:/i){
			@splitLineTest2 = split(/Other references[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[2] = $splitLineTest2[0];
		}
		# This should never happen, but just in case
		else{
			$listOfItemsTest[2] = $splitLineTest[1];
		}
	}

	# If there is no Synopsis or Description, then put the <description> in the description field and null
	# in the synopsys field.
	elsif (!($paragraphTest =~ m/Description[\s]*:/i)){

		if ($paragraphTest =~ m/Solution[\s]*:/i){
			@splitLineTest = split(/Solution[\s]*:/, $paragraphTest);
			$listOfItemsTest[3] = $splitLineTest[0];
		}
		elsif ($paragraphTest =~ m/Risk factor[\s]*:/i){
			@splitLineTest = split(/Risk factor[\s]*:/, $paragraphTest);
			$listOfItemsTest[3] = $splitLineTest[0];
		}
		elsif ($paragraphTest =~ m/CVSS Base Score[\s]*:/i){
			@splitLineTest = split(/CVSS Base Scor[\s]*:/, $paragraphTest);
			$listOfItemsTest[3] = $splitLineTest[0];
		}
		elsif ($paragraphTest =~ m/Plugin output[\s]*:/i){
			@splitLineTest = split(/Plugin output[\s]*:/, $paragraphTest);
			$listOfItemsTest[3] = $splitLineTest[0];
		}
		elsif ($paragraphTest =~ m/CVE[\s]*:/i){
			@splitLineTest = split(/CVE[\s]*:/, $paragraphTest);
			$listOfItemsTest[3] = $splitLineTest[0];
		}
		elsif ($paragraphTest =~ m/BID[\s]*:/i){
			@splitLineTest = split(/BID[\s]*:/, $paragraphTest);
			$listOfItemsTest[3] = $splitLineTest[0];
		}
		elsif ($paragraphTest =~ m/Other references[\s]*:/i){
			@splitLineTest = split(/Other references[\s]*:/, $paragraphTest);
			$listOfItemsTest[3] = $splitLineTest[0];
		}
		# In this case, there is just a description
		else{
			$listOfItemsTest[3] = $paragraphTest;
		}
	}
	

	# Parse out the Description verbiage, if any
	if ($paragraphTest =~ m/Description[\s]*:/i){
		@splitLineTest = split(/Description[\s]*:/, $paragraphTest);
		if ($paragraphTest =~ m/Solution[\s]*:/i){
			@splitLineTest2 = split(/Solution[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[3] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Risk factor[\s]*:/i){
			@splitLineTest2 = split(/Risk factor[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[3] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/CVSS Base Score[\s]*:/i){
			@splitLineTest2 = split(/CVSS Base Score[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[3] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Plugin output[\s]*:/i){
			@splitLineTest2 = split(/Plugin output[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[3] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/CVE[\s]*:/i){
			@splitLineTest2 = split(/CVE[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[3] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/BID[\s]*:/i){
			@splitLineTest2 = split(/BID[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[3] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Other references[\s]*:/i){
			@splitLineTest2 = split(/Other references[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[3] = $splitLineTest2[0];
		}
		else{
			$listOfItemsTest[3] = $splitLineTest[1];
		}
	}

	# Parse out the Solution verbiage, if any
	if ($paragraphTest =~ m/Solution[\s]*:/i){
		@splitLineTest = split(/Solution[\s]*:/, $paragraphTest);

		if ($paragraphTest =~ m/Risk factor[\s]*:/i){
			@splitLineTest2 = split(/Risk factor[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[4] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/CVSS Base Score[\s]*:/i){
			@splitLineTest2 = split(/CVSS Base Score[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[4] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Plugin output[\s]*:/i){
			@splitLineTest2 = split(/Plugin output[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[4] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/CVE[\s]*/i){
			@splitLineTest2 = split(/CVE[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[4] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/BID[\s]*:/i){
			@splitLineTest2 = split(/BID[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[4] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Other references[\s]*:/i){
			@splitLineTest2 = split(/Other references[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[4] = $splitLineTest2[0];
		}
		# This should never happen, but just in case
		else{
			$listOfItemsTest[4] = $splitLineTest[1];
		}
	}

	# Parse out the Risk Factor verbiage, if any
	if ($paragraphTest =~ m/Risk factor[\s]*:/i){
		@splitLineTest = split(/Risk factor[\s]*:/, $paragraphTest);
		if ($paragraphTest =~ m/CVSS Base Score[\s]*:/i){
			@splitLineTest2 = split(/CVSS Base Score[\s]*:/, $splitLineTest[1]);
			# When there is a CVSS Base Score, there will be a trailing: <space>/<space>
			$splitLineTest2[0] = substr($splitLineTest2[0], 0, -3);
			if ($splitLineTest2[0] =~ m/none/i){
				$listOfItemsTest[5] = "None";
			}
			elsif ($splitLineTest2[0] =~ m/low/i){
				$listOfItemsTest[5] = "Low";
			}
			elsif ($splitLineTest2[0] =~ m/medium/i){
				$listOfItemsTest[5] = "Medium";
			}
			elsif ($splitLineTest2[0] =~ m/high/i){
				$listOfItemsTest[5] = "High";
			}
		}
		elsif ($paragraphTest =~ m/Plugin output[\s]*:/i){
			@splitLineTest2 = split(/Plugin output[\s]*:/, $splitLineTest[1]);
			if ($splitLineTest2[0] =~ m/none/i){
				$listOfItemsTest[5] = "None";
			}
			elsif ($splitLineTest2[0] =~ m/low/i){
				$listOfItemsTest[5] = "Low";
			}
			elsif ($splitLineTest2[0] =~ m/medium/i){
				$listOfItemsTest[5] = "Medium";
			}
			elsif ($splitLineTest2[0] =~ m/high/i){
				$listOfItemsTest[5] = "High";
			}
		}
		elsif ($paragraphTest =~ m/CVE[\s]*:/i){
			@splitLineTest2 = split(/CVE[\s]*:/, $splitLineTest[1]);
			if ($splitLineTest2[0] =~ m/none/i){
				$listOfItemsTest[5] = "None";
			}
			elsif ($splitLineTest2[0] =~ m/low/i){
				$listOfItemsTest[5] = "Low";
			}
			elsif ($splitLineTest2[0] =~ m/medium/i){
				$listOfItemsTest[5] = "Medium";
			}
			elsif ($splitLineTest2[0] =~ m/high/i){
				$listOfItemsTest[5] = "High";
			}
		}
		elsif ($paragraphTest =~ m/BID[\s]*:/i){
			@splitLineTest2 = split(/BID[\s]*:/, $splitLineTest[1]);
			if ($splitLineTest2[0] =~ m/none/i){
				$listOfItemsTest[5] = "None";
			}
			elsif ($splitLineTest2[0] =~ m/low/i){
				$listOfItemsTest[5] = "Low";
			}
			elsif ($splitLineTest2[0] =~ m/medium/i){
				$listOfItemsTest[5] = "Medium";
			}
		elsif ($splitLineTest2[0] =~ m/high/i){
				$listOfItemsTest[5] = "High";
			}
		}
		elsif ($paragraphTest =~ m/Other references[\s]*:/i){
			@splitLineTest2 = split(/Other references[\s]*:/, $splitLineTest[1]);
			if ($splitLineTest2[0] =~ m/none/i){
				$listOfItemsTest[5] = "None";
			}
			elsif ($splitLineTest2[0] =~ m/low/i){
				$listOfItemsTest[5] = "Low";
			}
			elsif ($splitLineTest2[0] =~ m/medium/i){
				$listOfItemsTest[5] = "Medium";
			}
			elsif ($splitLineTest2[0] =~ m/high/i){
				$listOfItemsTest[5] = "High";
			}
		}
		# if there arent any more delimiters
		else{
			if ($splitLineTest2[1] =~ m/none/i){
				$listOfItemsTest[5] = "None";
			}
			elsif ($splitLineTest2[1] =~ m/low/i){
				$listOfItemsTest[5] = "Low";
			}
			elsif ($splitLineTest2[1] =~ m/medium/i){
				$listOfItemsTest[5] = "Medium";
			}
			elsif ($splitLineTest2[1] =~ m/high/i){
				$listOfItemsTest[5] = "High";
			}
		}
	}

	# Parse out the CVSS Base Score verbiage, if any
	if ($paragraphTest =~ m/CVSS Base Score[\s]*:/i){
		@splitLineTest = split(/CVSS Base Score[\s]*:/, $paragraphTest);
		if ($paragraphTest =~ m/Plugin output[\s]*:/i){
			@splitLineTest2 = split(/Plugin output[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[6] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/CVE[\s]*:/i){
			@splitLineTest2 = split(/CVE[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[6] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/BID[\s]*:/i){
			@splitLineTest2 = split(/BID[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[6] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Other references[\s]*:/i){
			@splitLineTest2 = split(/Other references[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[6] = $splitLineTest2[0];
		}
		# This should never happen, but just in case
		else{
			$listOfItemsTest[6] = $splitLineTest[1];
		}
	}

	# Parse out the Plugin output verbiage, if any
	if ($paragraphTest =~ m/Plugin output[\s]*:/i){
		@splitLineTest = split(/Plugin output[\s]*:/, $paragraphTest);
		if ($paragraphTest =~ m/CVE[\s]*:/i){
			@splitLineTest2 = split(/CVE[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[7] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/BID[\s]*/i){
			@splitLineTest2 = split(/BID[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[7] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Other references[\s]*:/i){
			@splitLineTest2 = split(/Other references[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[7] = $splitLineTest2[0];
		}
		# This should never happen, but just in case
		else{
			$listOfItemsTest[7] = $splitLineTest[1];
		}
	}

	# Parse out the CVE verbiage, if any
	if ($paragraphTest =~ m/CVE[\s]*:/i){
		@splitLineTest = split(/CVE[\s]*:/, $paragraphTest);
		if ($paragraphTest =~ m/BID[\s]*:/i){
			@splitLineTest2 = split(/BID[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[8] = $splitLineTest2[0];
		}
		elsif ($paragraphTest =~ m/Other references[\s]*:/i){
			@splitLineTest2 = split(/Other references[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[8] = $splitLineTest2[0];
		}
		# This should never happen, but just in case
		else{
			$listOfItemsTest[8] = $splitLineTest[1];
		}
	}

	# Parse out the BID verbiage, if any
	if ($paragraphTest =~ m/BID[\s]*:/i){
		@splitLineTest = split(/BID[\s]*:/, $paragraphTest);
		if ($paragraphTest =~ m/Other references[\s]*:/i){
			@splitLineTest2 = split(/Other references[\s]*:/, $splitLineTest[1]);
			$listOfItemsTest[9] = $splitLineTest2[0];
		}
		# This should never happen, but just in case
		else{
			$listOfItemsTest[9] = $splitLineTest[1];
		}
	}

	# Parse out the Other references verbiage, if any
	if ($paragraphTest =~ m/Other references[\s]*:/i){
		@splitLineTest = split(/Other references[\s]*:/, $paragraphTest);
		$listOfItemsTest[10] = $splitLineTest[1];
	}


	# One-Offs
	# If there are one-off cases, where the generic rules don't apply, place the code here
	
	return @listOfItemsTest;
} # Ends sub parse_paragraph_test



# Sorts by IP Address then Risk
# Expected Input format is an array of strings
# The strings are delimited by the vertical bar: |
sub sortIPAndRisk{
	my @listOfVulns1 = @_;
	my (@oldSorted1, @fields1, @fields2, $padIP1, $padIP2, $res1, $res2);

	@oldSorted1 = sort{@fields1 = parse_vertical_bar($a);
	@fields2 = parse_vertical_bar($b);
	$padIP1 = padIP($fields1[0]);
	$padIP2 = padIP($fields2[0]);
	$res1 = $padIP1 cmp $padIP2;

	# if the IP addresses are not the same
	if ($res1 != 0){
		return $res1;
	}
	# if the IP addresses are the same, sort based on risk
	else{
		$res2 = 0;
		return $res2;
	}
	} @listOfVulns1;

	return @oldSorted1;
}


# Compares the the two risk factor strings passed and returns 1, 0, -1 based on the following:
# High < Medium < Low < None < <blank>
sub riskCompare{
	my $risk1 = shift;
	my $risk2 = shift;
	my $one = 1;
	my $zero = 0;
	my $negative = 0 - 1;
	my $retV;

	# The two strings are equal
	if (($risk1 cmp $risk2) == 0){
		$retV = $risk1 cmp $risk2;
		return $retV;
	}

	if ($risk1 eq 'High'){
		return $negative;	
	}

	elsif ($risk1 eq 'Medium'){
		if ($risk2 eq 'High'){
			return 1;
		}
		else{
			return $negative;
		}
	}

	elsif ($risk1 eq 'Low'){
		if (($risk2 eq 'High') || ($risk2 eq 'Medium')){
			return 1;
		}
		else{
			return $negative;
		}
	}

	elsif ($risk1 eq 'None'){
		if (($risk2 eq 'High') || ($risk2 eq 'Medium') || ($risk2 eq 'Low')){
			return 1;
		}
		else{
			return $negative;
		}
	}

	else{
		if (($risk2 eq 'High') || ($risk2 eq 'Medium') || ($risk2 eq 'Low')){
			return $negative;
		}
		else{
			return $risk1 cmp $risk2;
		}

	}

	# Should never happen
	return 0;
}


# Sorts by IP Address then Port
# Expected Input format is an array
# @_[0] = IP Address		Ex: 192.168.1.1
# @_[1] = Port			Ex: 80/tcp
# @_[2] = Service Detected	Ex: http
sub sortIPs{
	my @listOfVulns = @_;
	my ($port1, $port2, $padIP1, $padIP2, $res1, @fields1, @fields2, @listOfVulns, @oldSorted);

	@oldSorted = sort{@fields1 = parse_csv($a);
		@fields2 = parse_csv($b);
		$padIP1 = padIP($fields1[0]);
		$padIP2 = padIP($fields2[0]);
		$res1 = $padIP1 cmp $padIP2;
		# if the IP addresses are not the same
		if ($res1 != 0){
			return $res1;
		}
		# if the IP addresses are the same, sort based on port number
		else{
			$port1 = portNum($fields1[1]);
			$port2 = portNum($fields2[1]);
			return $port1 <=> $port2;
		}
	} @listOfVulns;

	return @oldSorted;
}

# Takes in a string <port number>/<protocol> and returns the port number only
# Ex: 80/tcp
sub portNum{
	my $portString = shift;
	my @splitText = split(/\//, $portString);	

	return $splitText[0];
}

# Pads an IP address string with 0's for sorting purposes
sub padIP{
	my $ipIn = shift;
	my ($line2, $len, $len2);
	my @splitLine = split(/\./, $ipIn);
	foreach my $splitS (@splitLine){
		$len = length($splitS);
		if ($len == 1){
			$line2 = $line2 . "00" . $splitS;
		}
		elsif ($len2 == 2){
			$line2 = $line2 . "0" . $splitS;
		}
		else{
			$line2 = $line2 . $splitS;
		}
		$line2 = $line2 . ".";
	}
	chop($line2);
	return $line2;
}

# Takes in a string and parses by the vertical bar character: |
sub parse_vertical_bar{
	my $text = shift;
	my @new = ();
	push(@new, $+) 
	while $text =~m{
			"([^\"\\]*(?:\\.[^\"\\]*)*)",?
				| ([^|]+),?
				|,
			}gx;
	push(@new,undef) 
	if substr($text, -1, 1) eq '|';
		return @new;	
}


# Takes in a string and parses by the comma character: ,
sub parse_csv{
	my $text = shift;
	my @new = ();
	push(@new, $+) 
	while $text =~m{
			"([^\"\\]*(?:\\.[^\"\\]*)*)",?
				| ([^,]+),?
				|,
			}gx;
	push(@new,undef) 
	if substr($text, -1, 1) eq ',';
		return @new;	
}



# Return an array of the strings in port/service element field
sub parse_port_element{
	my $whole_text = shift;

	chomp $whole_text;
	# Removes the Windows newline character ^M or \r
	$whole_text=~s/\r//g;
	# splits into:
	# $first_split[0] = Service Name
	# $first_split[1] = <port number>/<protocol>
	my @first_split = split(/ /, $whole_text);

	# Remove the parenthesis around the port and protocol
	$first_split[1] = substr($first_split[1],1,-1);

	# splits into:
	# $second_split[0] = <port number>
	# $second_split[1] = <protocol
	my @second_split = split(/\//, $first_split[1]);
	
	my @return_port_service = ($first_split[0], $second_split[0], $second_split[1]);

	return @return_port_service;
}


