#!/usr/bin/env perl
# Copyright 2017 Frank Breedijk, Dan McGinn-Combs, Glenn ten Cate (blabla1337)
#
# 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 will call the Nikto scanner and import the results as IVIL
# ------------------------------------------------------------------------------

use strict;
use SeccubusV2;
use Seccubus::IVIL;
use Seccubus::Helpers;

use Getopt::Long;
use Carp;

my (
	$nikto_path,
	$nikto_options,
	$hosts_file,
	$workspace,
	$scan,
	$nodelete,
	$remote,
	$help,
	$verbose,
	$quiet,
   );

$help = 0;
$nodelete = undef;

# Create default values

GetOptions(
		'nikto_path|p=s'	=> \$nikto_path,
		'nikto_options|o=s'	=> \$nikto_options,
		'hosts=s'		=> \$hosts_file,
		'workspace=s'		=> \$workspace,
		'scan=s'		=> \$scan,
		'nodelete!'		=> \$nodelete,
		'remote|r=s'            => \$remote,
		'verbose|v+'		=> \$verbose,
		'quiet|q!'		=> \$quiet,
		'help|h'		=> \$help,
	  );

help() if $help;
$verbose = 0 if $quiet;

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";

print "Hosts file specified $hosts_file\n" if $verbose;
if ( ! $hosts_file || ! -e $hosts_file ) {
	print "You must specify a valid hosts file";
	help();
} elsif ( ! $workspace ) {
	print "You must specify a workspace name";
	help();
} elsif ( ! $scan ){
	print "You must specify a scan name";
	help();
};

$nikto_path = get_nikto_path() unless $nikto_path;
if ( ! $nikto_path && -e $nikto_path ) {
	print "Unable to find nikto on your system. I suggest you use the --nikto_path option\nto provide a path to nikto or nikto.pl\n";
	help();
}
print "Nikto found in $nikto_path\n" if $verbose;
my $nikto_version = get_nikto_version($nikto_path);

# This should fix issue #16
if ( $nikto_version =~ /^ERROR/ ) {
	print "There is a problem with your Nikto setup\n";
	print "Note: The --nikto_options parameter can be used to pass options to Nikto itself\n";
	print "$nikto_version";
	help();
}

print "Nikto version is $nikto_version\n" if $verbose;
if ( check_nikto_version($nikto_version) ) {
	print "Nikto version $nikto_version is ok\n" if $verbose;
} else {
	print "Your nikto version $nikto_version is too low. You should at least have nikto\nversion 2.1.2. Please upgrade\n";
	help();
}

my $tempfile = "/tmp/seccubus.$$";

my $nikto_options = "-h $hosts_file -o $tempfile.nbe -F nbe $nikto_options";
print "Nikto options: $nikto_options\n" if $verbose;

my $timestamp = make_timestamp();
print "Timestamp = $timestamp\n" if $verbose;

print "Execuing $nikto_path $nikto_options\n" unless $quiet;
run_cmd("$nikto_path $nikto_options",$verbose,$remote,[ $tempfile,"$tempfile.*" ], [ $hosts_file ]);

print "Scanning done, converting .nbe to ivil\n" unless $quiet;
my $cmd = "perl -I$config->{paths}->{modules} $config->{paths}->{bindir}\/nbe2ivil --scanner=Nikto --scannerversion=$nikto_version --workspace '$workspace' --scan '$scan' --timestamp=$timestamp --infile '$tempfile.nbe' ";
$cmd .= "-v" if $verbose > 1;
run_cmd($cmd,$verbose);

print "Importing ivil\n" unless $quiet;
$cmd = "$load_ivil --workspace '$workspace' --scan '$scan' --scanner Nikto --scannerversion $nikto_version --timestamp $timestamp";
$cmd .= " -v" if $verbose > 1;
$cmd .= " '$tempfile.ivil.xml'";
run_cmd($cmd,$verbose);

print "Scan imported, adding files to scan $scan in workspace $workspace\n" unless $quiet;

$cmd = "$attach_file --workspace '$workspace' --scan '$scan' --timestamp $timestamp --file '$tempfile' --description 'Command line output'";
$cmd .= " -v" if $verbose > 1;
run_cmd($cmd,$verbose);

$cmd = "$attach_file --workspace '$workspace' --scan '$scan' --timestamp $timestamp --file '$tempfile.nbe' --description 'NBE output'";
$cmd .= " -v" if $verbose > 1;
run_cmd($cmd,$verbose);

$cmd = "$attach_file --workspace '$workspace' --scan '$scan' --timestamp $timestamp --file '$tempfile.ivil.xml' --description 'IVIL output'";
$cmd .= " -v" if $verbose > 1;
run_cmd($cmd,$verbose);

# Cleanup
unless ( $nodelete ) {
	unlink "$tempfile" if -e "$tempfile";
	unlink "$tempfile.nbe" if -e "$tempfile.nbe";
	unlink "$tempfile.ivil.xml" if -e "$tempfile.ivil.xml";
}
if ( $remote ) {
	run_cmd("rm -f $tempfile $tempfile.*",$verbose,$remote);
}

exit(0);

sub help() {
	print "

Usage: scan 	[--nikto_path|p <path to nikto>] \\
		[--nikto_options '<additional nikto options>'] \\
		--hosts <hosts file> [--remote|r <host>,<user>,<key>] [--verbose|v] [--quiet|q]
		[--help|h]

Arguments:
--nikto_path	- You can use this optional parameter to provide the script with
(-p)		  the path to nikto.pl or nikto. If you do not provide this the
		  script tries to find the files itself and fails if it cannot
		  fidn them.
--nikto_options	- Additional command line options to provide to nikto see
(-o)		  'nikto -Help' for more information. Please quote the options so that they are
		  passed to Nikto as a single block. E.g. -o '--port 80,443 --ssl'
--hosts		- The file containing the 'hosts' to scan
--workspace	- Name of the workspace to load the findings into
--scan		- Name of the scan to load the findings into
--remote (-r)	- Comma separated list of hostname, username and key used to ssh into the host
		  specified and run the command
--verbose (-v)	- Be verbose during execution
--nodelete	- Don't delete temporary files
--quiet (-q)	- Don't print output
--help (-h)	- Print this message
";
	exit(1);
}

sub get_nikto_path() {
	my $path = run_cmd("which nikto",0,$remote);
	$path =~ s/[\n\r]*//g;
	if ( $path =~ /(^\/.*nikto)/ ) {
		return $1;
	}
	$path = run_cmd("which nikto.pl",0,$remote);
	$path =~ s/[\n\r]*//g;
	if ( $path =~ /(^\/.*nikto\.pl)/ ) {
		return $1;
	}
	$path = run_cmd("ls /opt/nikto/nikto.pl",0,$remote);
	if ( $path eq '/opt/nikto/nikto.pl' ) {
		return '/opt/nikto/nikto.pl';
	}
	return;
}

sub get_nikto_version() {
	my $nikto_path = shift;

	# Added some error handling to fix #16
	my $version=run_cmd("$nikto_path -Version 2>&1",0,$remote);
	if ( $version =~ /Nikto main\s+(\d+\.\d+\.\d+)/ ) {
		return $1;
	} else {
		return "ERROR: $version";
	}
}

sub check_nikto_version() {
	my $version = shift;
	$version =~ /(\d+)\.(\d+)\.(\d+)/;
	if ( ( $1 > 2 ) ||
	     ( $1 == 2 && $2 > 1 ) ||
	     ($1 == 2 && $2 == 1 && $3 >= 2 )
	   ) {
		return 1;
	} else {
		return 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";
}

