#!/usr/bin/perl
################################################################################
#			      Last update: 09.10.2004			      #
#-----------------------------------------------------------------------------#
#				     Inprotect				      #
#-----------------------------------------------------------------------------#
# Copyright (C) 2002 - 2004 Inprotect.com				      #
#									      #
# This program is free software; you can redistribute it and/or modify it     #
# under the terms of version 2 of the GNU General Public License as published #
# by the Free Software Foundation.					      #
#									      #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
# more details.								      #
#									      #
# You should have received a copy of the GNU General Public License along     #
# with this program; if not, write to the Free Software Foundation, Inc.,     #
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA			      #
#									      #
# Contact Information:							      #
# Alex Zimin (alex@inprotect.com)					      #
# http://www.inprotect.com						      #
###############################################################################
# See the README.txt and/or help files for more information on how to use &   #
# configuration.							      #
# See the LICENSE.txt file for more information on the License this software  #
# is distributed under.							      #
#									      #
# This program is intended for use in an authorized manner only, and the      #
# author can not be held liable for anything done with this program, code, or #
# items discovered with this program's use.				      #
###############################################################################

use strict;
use warnings;

use vars qw/%CONFIG/;

&load_configs;

#Redirect output to the log file
my $portscanlog = "$CONFIG{'PORTSCANLOG'}";
open(LOG,">>$portscanlog") or die "Failed to create $portscanlog: $!\n";
*STDERR=*LOG;
*STDOUT=*LOG;

use DBI;
use Parallel::ForkManager;
use MIME::Lite;

use constant MAX_FORKS => 5;
use constant DEBUG => 1;
use constant TRUE => 1;
use constant FALSE => 0;

## Call main subroutine
main();

close LOG;

exit(0);

# Main subroutine
sub main {
	my ($dbh, $sth_sel, $sth_upd, $sth_upd1);
	my ($options, $id, $username, $host, $email, $ports, $portstart, $portend, $common );
	my ($pm, $outfile, $timeout, $scanq);
	my $outputdir = "/usr/data/nmap";
	my ($fromemail, $resulturl);

	# Open a connection to the database
	$dbh = conn_db();

	# Get URL / email details from config file
	#
	$sth_sel=$dbh->prepare(qq{
		SELECT mailfrom, inprotect_url
		FROM inprotect_settings
		});
	$sth_sel->execute;

	($fromemail, $resulturl)=$sth_sel->fetchrow_array;

	$sth_sel = $dbh->prepare(q{
		SELECT count(id)
		FROM portscan
		WHERE scancomplete = "R"
	} );

	$sth_sel->execute;

	$scanq=$sth_sel->fetchrow_array;

	$scanq=$CONFIG{'MAXPORTSCANS'}-$scanq;

	if ($scanq<=0) { exit(0);}



	# Open a ForkManager handle limiting the number of children
	# to MAX_FORKS
	$pm = new Parallel::ForkManager($scanq);

	# Timeout for individual nmap scans set to 6hrs
	$timeout = 7800;

	## Declare and prepare cursors
	$sth_sel = $dbh->prepare(qq{
		SELECT id, username, scanip, email, ports, portstart, portend, common, options
		FROM portscan
		WHERE scancomplete = "N"
		LIMIT 0, $scanq
		} );
	$sth_upd = $dbh->prepare( q{
		UPDATE portscan 
		SET scancomplete = "Y"
		WHERE id = ?
		});

	$sth_sel->execute;
	while ( my ($id, $username, $host, $email, $ports, $portstart, $portend, $common, $options ) = $sth_sel->fetchrow_array ) {

		$pm->start and next; # do the fork

		my $dbhf;

		$dbhf = DBI->connect("$CONFIG{'DATABASEDSN'}:$CONFIG{'DATABASENAME'}:$CONFIG{'DATABASEHOST'}","$CONFIG{'DATABASEUSER'}","$CONFIG{'DATABASEPASSWORD'}", {
			PrintError => 0,
			RaiseError => 1,
			AutoCommit => 1 } ) or die("Failed to connect : $DBI::errstr\n");

		$sth_upd1 = $dbh->prepare(
			qq{ UPDATE portscan SET scancomplete="R"
			WHERE id = $id
			});
		$sth_upd1->execute;

		if ( DEBUG ) {
			print localtime(time)." port_scan: ";
			print join("|", $username, $host, $email, $ports, $portstart, $portend, $common ),"\n";
		}

		$outfile = "${outputdir}/nmap_${id}.out";
		unlink $outfile if -e $outfile;

		send_email($fromemail, $email, $username, "Port Scan Started", "Dear $username,\n\nPort scan has started");

		if ( run_nmap($host, $ports, $portstart, $portend, $common, $outfile,$timeout, $dbhf, $id, $options) ) {

			# Send email only on a successful nmap scan
			send_email($fromemail, $email, $username, "Port scan has finished", "Dear $username,\n\nPort scan has finished sucsessufully. You can access the scan results here:\n$resulturl/portscanres.php?op=view&pid=$id");

			# Mark the scan as completed
			# Commented by Ajay to not update the table during test runs
		}
		else {
			send_email($fromemail, $email, $username, "Port scan failed", "Dear $username,\n\nPort scan has failed. It may be due to the portscan timeout.");
		};
		$sth_upd->execute($id);
		#delete output file after scan
		unlink $outfile if -e $outfile;
		$pm->finish; # do the exit in the child process
	}

	# Wait for all spawned children to die(cruel Huh?)
	$pm->wait_all_children;
	disconn_db($dbh);
}


## Connect to database, returns database handle
sub conn_db {
	my ($dbh);
	$dbh = DBI->connect("$CONFIG{'DATABASEDSN'}:$CONFIG{'DATABASENAME'}:$CONFIG{'DATABASEHOST'}","$CONFIG{'DATABASEUSER'}","$CONFIG{'DATABASEPASSWORD'}", {
	PrintError => 0,
	RaiseError => 1,
	AutoCommit => 1 } ) or die("Failed to connect : $DBI::errstr\n");
	return $dbh;
}

## Disconnects from database
sub disconn_db {
	my ( $dbh ) = @_;
	$dbh->disconnect or die("Failed to disconnect : $DBI::errstr\n");
}
 

sub run_nmap {
	my ( $host, $ports, $portstart, $portend, $common, $outfile, $timeout, $dbhf, $id, $options) = @_;

	my $sth_ins;
#	my $cmd = "nmap -O -P0";
	my $cmd = "nmap -n -O".$options;
	if ( $ports eq "T" ) {
		$cmd .= " -sS";
	} elsif ( $ports eq "U" ) {
		$cmd .= " -sU";
	}
	else {
		$cmd .= " -sS -sU";
	}
	if ( $common eq "N" ) {
		$cmd .= " -p ${portstart}-${portend}";
	}

	$cmd .= " --host_timeout 7200000 $host";

	print localtime(time)." port_scan: $cmd\n" if DEBUG;
	eval {
		local $SIG{ALRM} = sub { die "nmap_timeout\n" };
		alarm $timeout;		# Set alarm to $timeout


		open(PROC,"$cmd |") or die "failed to fork :$!\n";
		my ($version, $port, $type, $status, $description,$nhost, $trash, $os);
		while(<PROC>){

			if (/Interesting/) {
				if (/(\d+\.\d+\.\d+\.\d+)/) {
					$nhost=$1;
				}
			}
			elsif (m!(\d+)/(\w+)\s+(\w+)\s+(\w+)\s+(.*)!g) {
				$port=$1;
				$type=$2;
				$status=$3;
				$description=$4;
				$version=$5;

				$sth_ins=$dbhf->prepare(qq{
					insert into portscan_results 
					values ($id, '$nhost', $port, '$type', '$status', '$description', '$version')});
				$sth_ins->execute;
			}
			elsif(/Running|guess/) {
				($trash, $os) = split (/:/, $_);
#				$os=$1;
				$sth_ins=$dbhf->prepare(qq{
					insert into portscan_os (id, host, os)
					values ($id, '$nhost', '$os')});
				$sth_ins->execute;
			}
		}
		close PROC;



#		 system($cmd) and die "Failed to run $cmd : ", $? >> 8 , "\n";
		 alarm 0;	 # Reset alarm
	};
	if ($@ eq "nmap_timeout\n") {
#		print localtime(time)." port_scan: -- $@\n";
		# For nmap timeout, write an extra message to STDERR
		warn "NMAP command timed out for $host\n" if $@ eq "nmap_timeout\n";
		#my $killprog="ps -C '$cmd' | perl -pe '(\$_)=/(\\d+)/;`kill -9 \$_`'";
		my $killprog="ps -auxww | grep '$cmd' | perl -pe '(\$_)=/root {3,9}(\\d+)/;`kill -9 \$_`'";
		#system($killprog);
		#system($killprog);
		return FALSE;
	}
	else {
		return TRUE;
	}
}


sub load_configs
{
	my $configfile="/usr/local/etc/inprotect.cfg";
	my $noconfig=0;
	open(CONF,"<$configfile") || $noconfig++;
	my @CONFILE=<CONF>;
	close(CONF);

	if ($noconfig) { print localtime(time)." port_scan: No config.txt file found.\n"; }

	foreach my $line (@CONFILE)
	{
		$line =~ s/\#.*$//;
		chomp($line);
		$line =~ s/\s+$//;
		$line =~ s/^\s+//;
		if ($line eq "") { next; }
		my @temp=split(/=/,$line,2);
		if ($temp[0] ne "") { $CONFIG{$temp[0]}=$temp[1]; }
	}

	return;
}

sub send_email {

	my ( $fromemail_addr, $email_addr, $username, $subject, $data) = @_;
	my ( $msg, $html_data );
	$msg = MIME::Lite->new(
		 To      =>"$email_addr",
		 From    =>"$fromemail_addr",
		 Subject =>"$subject",
		 Data    =>"$data"
		 );
	$msg->send();
}
