#!/usr/bin/env perl
# Copyright 2017 Fabien Guillaume, Frank Breedijk, Andrey Danin, Floris Kraak
#
# 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 script use Winfried Neessen <wn@neessen.net> perl module (see http://search.cpan.org/dist/OpenVAS-OMP/)
# to connect to an OpenVAS6 server via omp,
# initiate a scan and retrieve the report.
#  It then use the nbe2ivil script (included in Seccubus) to convert to Ivil format before import in Seccubus.
# ------------------------------------------------------------------------------

use strict;

use SeccubusV2;
use Seccubus::IVIL;
use Seccubus::Runs;
use Seccubus::Findings;
use OpenVAS::OMP;
use Getopt::Long;
use Carp;
use MIME::Base64;
use XML::Simple;
use Data::Dumper;

my (
	$server,
	$user,
	$password,
	$port,
	$policy,
	$targetip,
	$targetname,
	$portlist,
	$portlistname,
	$timeout,
	$verbose,
	$scan,
	$timestamp,
	$xml,
	$workspace,
	$sleep,
	$help,
	$quiet,
	$nodelete,
);

# default values for command line values :

$help = 0;
$quiet = 0;
$sleep = 10;
$targetip = 0;
$nodelete = undef;
$verbose = 0;

GetOptions (
	'workspace|ws=s'	=> \$workspace,
	'scan|sc=s'			=> \$scan,
	'server|s=s'		=> \$server,
	'port=s'			=> \$port,
	'user|u=s'			=> \$user,
	'password|pw=s'		=> \$password,
	'policy|pol=s'		=> \$policy,
	'target|t=s'		=> \$targetname,
	'targetip|tip=s'	=> \$targetip,
	'portlist=s'		=> \$portlistname,
	'portrange=s'		=> \$portlist,
	'sleep=s'			=> \$sleep,
	'timeout=s' 		=> \$timeout,
	'nodelete'			=> \$nodelete,
	'verbose|v+'		=> \$verbose,
	'quiet|q!'			=> \$quiet,
	'help'				=> \$help,
);

help() if $help;

$verbose = 0 if $quiet;

#if target name not specified, then it is the ip adress
$targetname=$targetip unless $targetname;
$portlistname=$portlist unless $portlistname;
$timeout=14400 unless $timeout;

help("You must specify a policy") unless $policy;
help("You must specify a user") unless $user;
help("You must specify a password") unless $password;
help("You must specify an OpenVAS server") unless $server;

$timestamp = make_timestamp();
print "timestamp : $timestamp\n" if $verbose > 0;

my $tempfile = "/tmp/seccubus.OV6.$scan.$timestamp.$$";

my $config = get_config();

my $load_ivil = "perl -I$config->{paths}->{modules} $config->{paths}->{bindir}\/load_ivil";
my $attach_file = "perl -I$config->{paths}->{modules} $config->{paths}->{bindir}\/attach_file";
my $nbe2ivil = "perl -I$config->{paths}->{modules} $config->{paths}->{bindir}\/nbe2ivil";

print "attach_file : $attach_file \n";
print "scan : $scan \n";
print "workspace : $workspace\n";

## Create new OpenVAS::OMP object
my $omp = OpenVAS::OMP->new(
	host            => $server,
	ssl_verify      => 0,
	username        => $user,
	password        => $password,
);

#Get Policy id from text

$xml = omp_request("<get_configs/>");
my $pol_id = $xml->{config}->{$policy}->{id};

if ( $pol_id ) {
	print "Policy ID is $pol_id\n" if $verbose >1;
} else {
	die "Could not find policy id for policy $policy";
}

my $portlist_id = "";
# Get port list id if a portlist is specified. If it doesn't exist create it
if ( $portlistname ) {
	print "Port list name : $portlistname \n" if $verbose >1;
	print "Port list      : $portlist \n" if $verbose >1;

	# Get all portlists
	$xml = omp_request("<get_port_lists/>");
	if ( exists $xml->{port_list}->{$portlistname} ) {
		$portlist_id = $xml->{port_list}->{$portlistname}->{id};
	} else {
		# We need to create it
		$xml = omp_request("
			<create_port_list>
				<name>$portlistname</name>
				<comment>Created by Seccubus</comment>
				<port_range>$portlist</port_range>
			</create_port_list>
		");
		if ( $xml->{status} == '201' ) {
			$portlist_id = $xml->{id};
		} else {
			die "Error creating portlist $portlistname: $xml->{status_text}";
		}
	}
}

print "Portlist ID is $portlist_id\n" if $verbose >1 && $portlist_id;

my $target_id = "";
# Get target id

print "Target    : $targetname \n" if $verbose >1;
print "Target ip : $targetip \n" if $verbose >1 && $targetip ne "0";

$xml = omp_request("<get_targets/>");
if ( exists $xml->{target}->{$targetname} ) {
	if ( $targetip ne "0" ) {
		if ( $xml->{target}->{$targetname}->{hosts} ne $targetip ) {
			die "Target '$targetname' found, but it doesn't match target ip '$targetip', please omit target IP specification or edit the target manualy in OpenVAS to correct the problem";
		}
	}
	if ( $portlist_id ) {
		if ( $xml->{target}->{$targetname}->{port_list}->{id} ne $portlist_id ) {
			die "Target '$targetname' found, but it doesn't match the specified port list, please omit port list specification or edit the target manualy in OpenVAS to correct the problem";
		}
	}
	$target_id = $xml->{target}->{$targetname}->{id};
} else {
	# Need to create a target
	if ( $targetip eq "0" ) {
		die "Need to create a new target '$targetname', but there was no target IP range specified.";
	}
	if ( ! $portlist_id ) {
		print "Need to create a new target '$targetname', but there was no port list specified.\nPort list will default to OpenVAS Default\n" if $verbose;
	}
	$xml = omp_request("
		<create_target>
			<name>$targetname</name>
			<hosts>$targetip</hosts>
			<port_list id=\"$portlist_id\"/>
		</create_target>
	");
	if ( $xml->{status} eq "201" ) {
		$target_id = $xml->{id};
	} else {
		die "Unable to create target '$targetname' with hosts '$targetip' and portlist '$portlist_id' ($portlistname): $xml->{status_text}";
	}
}
#die Dumper($xml);
print "Target ID : $target_id \n" if $verbose >1;

# Create scan task

my $task_id = "";
$xml = omp_request("
	<create_task>
		<name>Seccubus $targetname</name>
		<comment>$timestamp</comment>
		<config id='$pol_id' />
		<target id='$target_id' />
	</create_task>
");
if ( $xml->{status} eq "201" ) {
	$task_id = $xml->{id};
	print "Task ID   : $task_id\n" if $verbose >1;
} else {
	die "Unable to create task 'Seccubus $targetname': $xml->{status_text}";
}

#start task
my $report_id = "";
my $scan_status = "";
$xml = omp_request("<start_task task_id=\"$task_id\"/>");
if ( $xml->{status} eq "202" ) {
	print "$xml->{status_text}\n" if $verbose >1;
	$report_id = $xml->{report_id};
	$scan_status = $xml->{status};
}

print "Scan requested.\n" unless $quiet;
my $timecount=0;
while ($scan_status eq "202" || $scan_status eq "New" || $scan_status eq "Requested" ) {
	sleep($sleep);
	$timecount+=$sleep;
	if ( $timecount > $timeout ) {
		die "scanning for more than $timeout seconds, exiting on timeout \n";
	}
	$xml = omp_request("<get_tasks task_id='$task_id'/>");
	$scan_status = $xml->{task}->{status};
	print "Scan status: $scan_status\n" if $verbose >1;
}
while ($scan_status eq "Running" ) {
	sleep($sleep);
	$timecount+=$sleep;
	if ( $timecount > $timeout ) {
		die "scanning for more than $timeout seconds, exiting on timeout \n";
	}
	$xml = omp_request("<get_tasks task_id='$task_id'/>");
	$scan_status = $xml->{task}->{status};
	print "Scan status: $scan_status\n" if $verbose >1;
}
if ( $scan_status eq "Done") {
	print "Scan finished\n";
} else {
	die "Scan ended with unexpected status: '$scan_status'"
}

# get report format id for nbe, HTML and xml

my $formats = omp_request("<get_report_formats/>");

# Retrieve report nbe format

# NBE, HTML, XML, PDF, TXT
my @types = qw(nbe html xml txt);
foreach my $type ( @types ) {
	print "Getting $type report\n" if $verbose >1;
	$xml = omp_request("
		<get_reports report_id='$report_id' format_id='$formats->{report_format}->{uc($type)}->{id}'/>
	");
	if ( $xml->{status} eq "200" ) {
		open(my $OUT,">", "$tempfile.$type") or die "Unable to open file '$tempfile.$type' for write";
		print $OUT MIME::Base64::decode_base64($xml->{report}->{content});
		close $OUT;
	} else {
		die "Get report request failed with status '$xml->{status}' : $xml->{status_text}";
	}
}

#Convert scan result to Ivil
print "Converting scan results to IVIL\n" unless $quiet;
my $cmd = "$nbe2ivil --scanner=OpenVAS6 --timestamp=$timestamp --infile '$tempfile.nbe'";
$cmd .= " -v" ;
print "Executing $cmd\n" if $verbose > 1;
my $result = `$cmd 2>&1`;
print "$result\n" if $verbose > 1;

# Load the results
my ( $workspace_id, $scan_id, $run_id) = load_ivil("$tempfile.ivil.xml", "OpenVAS6", "", $timestamp, $workspace, $scan, $verbose );
# Process the results
process_status($workspace_id, $scan_id, $run_id, $verbose);

# Attach files
print "Attaching file '$tempfile.ivil.xml' to scan '$scan'\n" if $verbose;
update_run($workspace_id, $scan_id, $timestamp, "$tempfile.ivil.xml", "IVIL file");
foreach my $type ( @types ) {
	print "Attaching file '$tempfile.$type' to scan '$scan'\n" if $verbose;
	update_run($workspace_id, $scan_id, $timestamp, "$tempfile.$type", "Raw $type results");
}

unless ( $nodelete ) {
	foreach my $type ( @types ) {
		unlink "$tempfile.$type";
	}
	unlink "$tempfile.ivil.xml";
}

exit(0);

# Cleanup
unless ( $nodelete ) {
	unlink "$tempfile.nbe" if -e "$tempfile.nbe";
	unlink "$tempfile.pdf" if -e "$tempfile.pdf";
	unlink "$tempfile.html" if -e "$tempfile.html";
	unlink "$tempfile.txt" if -e "$tempfile.txt";
	unlink "$tempfile.ivil.xml" if -e "$tempfile.ivil.xml";
}



exit (0);

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;

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

sub omp_request() {
	my $request = shift;
	die "No omp request specified" unless $request;
	print "sumbitted omp request : $request\n" if $verbose >2;
	my $xmlrequest = $omp->commandXML($request,1);
	print "retreived omp result  : $xmlrequest\n" if $verbose >2;
	return XMLin($xmlrequest);
}

sub help {
	my $message = shift;

	print "Error: $message\n";

	print "
Usage: scan     --workspace=<seccubus workspace> --scan=<seccubus scan>
		        --server=<OpenVAS server> [--port=<OpenVAS server port>
		        --user=<OpenVAS user> --password=<OpenVAS password>
		        --policy=<OpenVAS Scan Config name> [--target=<OpenVAS target>]
		        [--targetip=<IP(s) to scan>] [--portlist=<OpenVAS port listname>]
		        [--portrange=<Port range(s) to scan>] [--sleep=<sleeptime>]
		        [--timeout=<timeout>] [--nodelete] [--verbose] [--quiet] [--help]

--workspace (-ws) - Seccubus workspace the scan should be in
--scan (-sc)	  - Seccubus scan the data should be saved in
--server          - IP address or hostname of OpenVAS scanner
--port            - TCP port the OpenVAS scanner is listening on (optional,
                    defaults=9390)
--user            - OpenVAS user name
--password        - OpenVAS password
--policy          - OpenVAS policy (aka Scan Config) to use for scanning
--target          - Name of the OpenVAS target to use when scanning (if the
	                target doesn't exist, it will be created using this name.
	                optional, default=<targetip)
--targetip        - Definition of the IPs to scan. (optional if target is
	                specified and exists)
--portlist        - Name of the OpenVAS portlist to use when scanning (if
	                the portlist doesn't existm it will be created using this
	                name. optional, default=use the portlist from the target
	                copy the name from portrange)
--portrange       - OpenVAS port range to scan (optional if a target or portlist
	                is specified and it exists)
--sleep           - Seconds to sleep between polls of the API  (optional,
	                default=10)
--timeout         - # Seconds after which to abort a scan (optional,
	                default=14400 (4 hours))
--nodelete        - Don't erase temporary files
--verbose (-v)    - Be verbose during execution (repeat to increase verbosity)
--quiet (-q)      - Don't print output
--help (-h)       - Print this message
	";

	exit(1);
}
