#!/usr/bin/env perl
# Copyright 2011-2017 Frank Breedijk, Andrey Danin, Glenn ten Cate
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ------------------------------------------------------------------------------
# This program converts an nmap.xml file to the IVIL format
# ------------------------------------------------------------------------------

use strict;
use SeccubusV2;
use IVIL;

use Getopt::Long;
use Carp;
use XML::Simple;
use Data::Dumper;

my (
	$scanname,
	$scanner,
	$scannerversion,
	$help,
	$verbose,
	$workspace,
	$timestamp,
	$infile,
	$outfile,
   );

$help = 0;

# Create default values
$scanner = "Nmap";
$timestamp = make_timestamp();

GetOptions(	'scan=s'		=> \$scanname,
		'scanner=s'		=> \$scanner,
		'scannerversion=s'	=> \$scannerversion,
		'help|h!'		=> \$help,
		'verbose|v!'		=> \$verbose,
		'workspace=s'		=> \$workspace,
		'timestamp=s'		=> \$timestamp,
		'infile=s'		=> \$infile,
		'outfile=s'		=> \$outfile,
	  );

help() if $help;
$scanname = $workspace unless $scanname;

if ( ! $infile || ! -e $infile ) {
	print "You must specify the infile parameter";
	help();
} elsif ( ( $workspace && ! $scanname ) || ( ! $workspace && $scanname ) ) {
	print "workspace and scan have to specified both or not at all";
	help();
};

print "Reading in nmap xml file\n" if $verbose;
# Ticket #74 - Minor bugs in nmap2ivil when using nmap 5.21
# https://sourceforge.net/apps/trac/seccubus/ticket/74
my $nmap = XMLin($infile,
			     forceArray	=> [ 'host',
			   					 'osclass',
			   					 'osmatch',
			   					 'port',
			   					 'extrareasons',
			   					 'address',
			   					 'hostname',
			   					 'extraports',
			   					 'script',
			   				   ],
			     KeyAttr	=> undef,
);
# Lets clear some memory space
#$nmap->{Policy} = undef;

unless ( $outfile ) {
	$outfile = $infile;
	$outfile =~ s/\.xml$//;
	$outfile .= ".ivil.xml";
}
print "Opening file $outfile for output\n" if $verbose;
open(my $OUT, ">", "$outfile") or die "Unable to open output file $outfile";
print $OUT xml_header();
print $OUT ivil_open();

if ($workspace) {
	print "Creating addressee block\n" if $verbose;
	print $OUT ivil_addressee("Seccubus", {
						"workspace" => $workspace,
						"scan"		=> $scanname,
			 		     });
}

print "Creating findings\n" if $verbose;
print $OUT "<findings>\n";

print "Handling scaninfo\n" if $verbose;
my $finding = {};
$finding->{ip} = "scaninfo";
$finding->{port} = "generic/$nmap->{scaninfo}->{protocol}";
$finding->{id} = "scaninfo";
$finding->{finding} = "
Protocol        : $nmap->{scaninfo}->{protocol}
Number of ports : $nmap->{scaninfo}->{numservices}
Ports scanned   : $nmap->{scaninfo}->{services}
Scan type       : $nmap->{scaninfo}->{type}
Nmap command    : $nmap->{args}";
print $OUT ivil_finding($finding);

print "Handling stats\n" if $verbose;
$finding = {};
$finding->{ip} = "scaninfo";
$finding->{port} = "generic/$nmap->{scaninfo}->{protocol}";
$finding->{id} = "stats";
$finding->{finding} = "Number of hosts
Scanned : $nmap->{runstats}->{hosts}->{total}
Up      : $nmap->{runstats}->{hosts}->{up}
Down    : $nmap->{runstats}->{hosts}->{down}";
print $OUT ivil_finding($finding);

print "Handling summary\n" if $verbose;
$finding = {};
$finding->{ip} = "scaninfo";
$finding->{port} = "generic/$nmap->{scaninfo}->{protocol}";
$finding->{id} = "summary";
# Ticket #74 - Minor bugs in nmap2ivil when using nmap 5.21
# https://sourceforge.net/apps/trac/seccubus/ticket/74
if ( ref($nmap->{runstats}->{finished}->{summary}) eq "" ) {
	$finding->{finding} = $nmap->{runstats}->{finished}->{summary};
} else {
	$finding->{finding} = "Scan Exection Stats\n";
	$finding->{finding} .= "Completed : $nmap->{runstats}->{finished}->{timestr}\n";
	$finding->{finding} .= "Duration  : $nmap->{runstats}->{finished}->{elapsed} seconds";
}

print $OUT ivil_finding($finding);

print "Iterating over hosts\n" if $verbose;

foreach my $host ( @{$nmap->{host}} ) {
	# Lets get the basic host parameters first
	my $ip = "";
	for my $addr ( @{$host->{address}} ) {
		print "Found addr $addr->{addr} with type $addr->{addrtype}\n" if $verbose;
		if ( $addr->{addrtype} eq "ipv6" ) {
			$ip = $addr->{addr};	# This values stores the IP address
		} elsif ( $addr->{addrtype} eq "ipv4" ) {
			$ip = $addr->{addr};	# This values stores the IP address
		}
	}

	if ( $ip eq "" ) {
		print "Failed to find IPv4/IPv6 address for host '$host->{hostnames}->{hostname}[0]->{name}'\n";
		next;
	}

	print "IP: $ip\n" if $verbose;
	foreach my $item ( keys %{$host} ) {
		# O.K. lets go through all findings
		$finding = {};
		$finding->{ip} = $ip;
		$finding->{id} = $item;
		$finding->{port} = "generic/$nmap->{scaninfo}->{protocol}";
		if ( $item eq 'tcpsequence' ) {
			$finding->{finding} = "TCP sequence\nIndex      : $host->{$item}->{index}\nDifficulty : $host->{$item}->{difficulty}";
		} elsif ( $item eq 'distance' ) {
			$finding->{finding} = "Distance : $host->{$item}->{value} hops";
		} elsif ( $item eq 'tcptssequence') {
		 	$finding->{finding} = "TCP Timestamp Sequence : $host->{$item}->{class}";
		} elsif ( $item eq 'ipidsequence' ) {
			 $finding->{finding} = "IP ID sequence : $host->{$item}->{class}";
		} elsif ( $item eq 'status') {
			$finding->{finding} = "Host status : $host->{$item}->{state}\nReason      : $host->{$item}->{reason}";
		} elsif ( $item eq 'hostnames') {
			$finding->{finding} = "Host names\n";
			foreach my $hostname ( @{$host->{$item}->{hostname}} ) {
				$finding->{finding} .= "$hostname->{name} - $hostname->{type}\n";
			}
		} elsif ( $item eq 'os') {
			$finding->{finding} = "OS Identification\n\nOS Matches :\n";
			foreach my $os ( @{$host->{$item}->{osmatch}} ) {
				$finding->{finding} .= "$os->{name} ($os->{accuracy}%)\n";
			}
			$finding->{finding} .= "\nOS Classes :\n";
			foreach my $osclass ( @{$host->{$item}->{osclass}} ) {
				$finding->{finding} .= "Type       : $osclass->{type}\nVendor     : $osclass->{vendor}\nFamily     : $osclass->{osfamily}\nGeneration : $osclass->{osgen}\nAccuracy   : $osclass->{accuracy}%\n\n";
			}
		} elsif ( $item eq 'ports') {
			foreach my $port ( @{$host->{$item}->{'port'}} ) {
				$finding->{port} = "$port->{portid}/$port->{protocol}";
				$finding->{finding} = "Port         : $port->{portid}/$port->{protocol} - $port->{state}->{state}\n";
				$finding->{finding} .= "State        : $port->{state}->{state}\n";
				$finding->{finding} .= "Confirmed by : $port->{state}->{reason} (TTL: $port->{state}->{reason_ttl})\n";
				$finding->{finding} .= "\n";
				$finding->{finding} .= "Service      : $port->{service}->{name}\n";
				$finding->{finding} .= "Tunnel       : $port->{service}->{tunnel}\n" if $port->{service}->{tunnel};
				$finding->{finding} .= "Method       : $port->{service}->{method}\n";
				$finding->{finding} .= "Product      : $port->{service}->{product}\n" if  $port->{service}->{product};
				$finding->{finding} .= "Hostname     : $port->{service}->{hostname}\n" if $port->{service}->{hostname};
				$finding->{finding} .= "Extra info   : $port->{service}->{extrainfo}\n" if $port->{service}->{extrainfo};
				$finding->{finding} .= "Fingerprint  : $port->{service}->{servicefp}\nPlease submit fingerprint to http://insecure.org/cgi-bin/submit.cgi" if $port->{service}->{servicefp};
				$finding->{finding} .= "Confidence   : $port->{service}->{conf}";
				print $OUT ivil_finding($finding) if $finding;
				if (exists $port->{script} ) {
					foreach my $script ( @{$port->{script}} ) {
						$finding->{id} = $script->{id};
						$finding->{finding} = $script->{output};
						print $OUT ivil_finding($finding);
					}
				} #script
			} #port

			$finding->{id} = "ports";
			$finding->{port} = "other";
			$finding->{finding} = "";
			foreach my $ep ( @{$host->{$item}->{extraports}}) {
				$finding->{finding} .= "$ep->{count} other ports are $ep->{state}\n\n";
				foreach my $extra ( @{$ep->{extrareasons}} ) {
					$finding->{finding} .= "$extra->{reason} : $extra->{count}\n";
				} #extrareasons
			} #extraports

		} elsif ( $item eq 'starttime') {
			$finding = undef;
		} elsif ( $item eq 'uptime') {
			$finding->{finding} .= "Uptime    : $host->{$item}->{seconds} seconds\nLast boot : $host->{$item}->{lastboot}";
		} elsif ( $item eq 'endtime') {
			$finding = undef;
		} elsif ( $item eq 'address') {
			$finding = undef;
		} elsif ( $item eq 'times') {
			$finding = undef;
		} else {
			print "Unknown item: $item\n" if $verbose;
			$finding = undef;
		}
		print $OUT ivil_finding($finding) if $finding;
	} #items
} #hosts

print $OUT "</findings>\n";

#print "Creating sender block\n" if $verbose;
#print $OUT ivil_sender($scanner, $scannerversion, $timestamp);

print $OUT ivil_close();

close $OUT;

exit();

sub help() {
	print "

Usage: nmap2ivil --scanner <scanner> [--scannerversion <versionstring>] \\
                --timestamp <timestamp> [--workspace <workspacename>] \\
		[--scan <scanname>] --infile <filename input> \\
		[--outfile <filename output>] [--verbose] [--help]

Arguments:
--scanner	- Optional: The name of the scanner used to create the nmap
		  .xml file. Defaults to 'Nmap'
--scannerversion- Optional: the version of the scanner used to create the nmap
		  .xml file
--timestamp	- Optional: Timestamp of when the file was created in the
		  format YYYYMMDDhhmmss or YYYYMMDDhhmm so
		  11 december 2011 1:14:00 pm is 20111211131400 or 201112111314
		  Defaults to the current date and time
--workspace	- Optional: Which Seccubus workspace do you want to load this
		  in, this informaiton is used to create the addressee block.
		  If no value is given for workspace no addressee block is
		  generated
--scan		- Optional: Which Seccubus scan do you want to load this in,
		  this informaiton is used to create the addressee block. If
		  scan is not specified then the value for workspace is used.
--infile	- This defines the .xml file that will be converted to IVIL
--outfile	- Optional: This defines the name of the file used to output
		  IVIL. If no filename is given, the infile value is used,
		  a trailing .xml is removed (if it exists) and .ivil.xml is
		  appended
--verbose (-v)	- Be verbose
--help (-h)	- Print this message
";
	exit();
}

sub make_timestamp() {
        my ($second, $minute, $hour, $day, $month, $year) = localtime();
	$month++;
	$second = "0" . $second if $second < 10;
	$minute = "0" . $minute if $minute <10;
	$hour = "0". $hour if $hour < 10;
	$day = "0". $day if $day <10;
	$month = "0" . $month if $month <10;
	$year += 1900;

	"$year$month$day$hour$minute$second";
}
