#!/usr/bin/perl
#
# $Id: sched.pl,v 1.4 2004/11/23 22:43:04 greg_k Exp $
#

################################################################################
#			      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 MIME::Lite;
use Date::Calc qw( Days_in_Month Day_of_Week Add_Delta_YMD);
use DBI;
use Socket;
#use NetAddr::IP;

#Declare constants
use constant TRUE => 1;
use constant FALSE => 0;
use constant DEBUG => 0;

#Read settings from the inprotect.cfg file
use vars qw/%CONFIG/;
load_configs();

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

print localtime(time)." sched: Start\n";

my($hostip);


my($startip, $endip, $startip_n, $endip_n, $ip1, $ip2, $ip3, $ip4, $ip5, $ip6, $ip7, $ip8, $oct1, $oct2, $oct3, $oct4, $var1, $var2, $scanip, $start_n, $end_n, $nip, $is_submitted, $sth_check);

my($noncomplete,$stopq, $stopid, $stopcmd, $schedid, $username, $email, $schedule_type, $day_of_week, $day_of_month, $time);
my ($stime, $scantime, $sid, $sth_sel, $pm);
my ($infield1, $infield2, $infield3, $infield4, $infield5, $infield6, $infield7, $insertid);
my ($id, $host, $str, $dbh, $sth, $sth_ins, $sth_upd, $timeout, $fromemail, $resulturl );


#Connecting to the database
$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");

# Read some more config data
$sth=$dbh->prepare(qq{
	SELECT mailfrom, inprotect_url
	FROM inprotect_settings
	});
$sth->execute;
($fromemail, $resulturl)=$sth->fetchrow_array;

# Get a list of schedules
$sth_sel=$dbh->prepare(qq{
	SELECT distinct sched_id
	FROM nessus_scan
	});
$sth_sel->execute;

# For each schedule
while(($schedid)=$sth_sel->fetchrow_array){
	# For the current schedule, how many scans are RUNNING or WAITING to run?
	$sth=$dbh->prepare(qq{
		SELECT count(*)
		FROM nessus_scan
		WHERE sched_id=$schedid
		AND not (status="C" OR status = "F")
		});
	$sth->execute;
	($noncomplete)=$sth->fetchrow_array;

	# If there are no scans for current schedule running, delete all scans for current schedid
	if ($noncomplete==0) {
		$sth=$dbh->prepare(qq{
			DELETE
			FROM nessus_scan
			WHERE sched_id=$schedid
				and NOT (status="R" OR status IS NULL)
			});
		$sth->execute;

		# Get details of who owns the completed scan
		$sth=$dbh->prepare(qq{
			SELECT t1.username, t2.pn_email, t1.next_scan
			FROM nessus_schedule t1, users t2
			WHERE t1.id=$schedid
			AND t1.username=t2.pn_uname
			});
		$sth->execute;
		my($user_sm, $email_sm, $scan_sm)=$sth->fetchrow_array;

		# Send them a report
		email_results($email_sm, $user_sm, $scan_sm, $dbh, $schedid, $fromemail, $resulturl);

		# List scans as "complete" for NOW or ONCE ONLY schedule items
		$sth=$dbh->prepare(qq{
			UPDATE nessus_schedule
			SET status="C"
			WHERE id=$schedid
			AND schedule_type="N"
			OR id=$schedid
			AND schedule_type="O"
			});
		$sth->execute;

		# For daily/weekly/monthly, set the status to NULL for completed scans
		$sth=$dbh->prepare(qq{
			UPDATE nessus_schedule
			SET status=null
			WHERE id=$schedid
			AND schedule_type="D"
			OR id=$schedid
			AND schedule_type="W"
			OR id=$schedid
			AND schedule_type="M"
			});
		$sth->execute;
	}
}

#Getting system time
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

$year+=1900;
my $now;
my $timenow;
$mon++;
$now = sprintf("%04d%02d%02d%02d%02d%02d",$year, $mon, $mday, $hour, $min, $sec);
$timenow = sprintf("%02d%02d%02d", $hour, $min, $sec);


my $sth1;
my $serverid;

# Get a list of "dead" scanning servers
$sth=$dbh->prepare(qq{
	SELECT id
	FROM nessus_servers
	WHERE checkin_time+300<now()
	});
$sth->execute;

# For each "dead" server...
while(($serverid)=$sth->fetchrow_array) {
	$sth1=$dbh->prepare(qq{
		UPDATE nessus_scan
		SET server_id=0, status="S", start_dttm=NULL, end_dttm=NULL
		WHERE server_id=$serverid
		AND status="R"
		OR server_id=$serverid
		AND status="S"
		});

	# Reschedule scan jobs so that another server might pick them up
	$sth1->execute;
	$sth1->finish;
}


#checking for the new unscheduled scans
$sth = $dbh->prepare(qq{
	SELECT id, username, schedule_type, day_of_week, day_of_month, time
	FROM nessus_schedule
	WHERE status is NULL
	} );
$sth->execute;

while(($schedid, $username, $schedule_type, $day_of_week, $day_of_month, $time)=$sth->fetchrow_array){
	$stime=substr($time,0,2).substr($time,3,2)."00";
	schedule_scan ($schedid, $username, $schedule_type, $stime, $day_of_week, $day_of_month);
};

#Send start scan email notifications
$sth=$dbh->prepare(qq{
	SELECT t2.pn_email, t2.pn_uname, t1.next_scan
	FROM nessus_schedule t1, users t2
	WHERE t1.username=t2.pn_uname
	AND t1.status="S"
	AND t1.next_scan <="$now"
	});
$sth->execute;

while (my($semail, $susername, $sscantime)=$sth->fetchrow_array) {
	scan_started_email($semail, $susername, $sscantime, $fromemail);
}

#Select scheduled scans to run.
$sth = $dbh->prepare(qq{
	SELECT h.hostip, h.sched_id
	FROM nessus_hosts h, nessus_schedule s
	WHERE h.sched_id = s.id
	AND s.status = "S" 
	AND s.next_scan <= "$now" 
	} );
$sth->execute;
$sth_upd=$dbh->prepare(qq{
	UPDATE nessus_schedule
	SET status="R"
	WHERE status="S"
	AND next_scan<= "$now"
	});
$sth_upd->execute;
$sth_upd->finish;

# Insert into nessus_scan
# 1. Split IP range into individual IP's before inserting into nessus_scan
# 2. Assign 
while(($hostip, $sid) = $sth->fetchrow_array ){
	print localtime(time)." sched: Checking scanid=$sid, hostip=$hostip\n";
	my ( $sth_ins_nessus_scan );

	$sth_ins_nessus_scan = $dbh->prepare(qq{
		INSERT INTO nessus_scan
		( sched_id, host, status, submit_dttm )
		VALUES(?,?,?,now())
		} );

	($startip, $endip)=split(/-/,$hostip);

	if (!defined($endip)) { 
		$endip=$startip;
	}

	$startip_n=inet_aton($startip);
	$endip_n=inet_aton($endip);

	($ip1, $ip2, $ip3, $ip4)=unpack("CCCC", $startip_n);
	($ip5, $ip6, $ip7, $ip8)=unpack("CCCC", $endip_n);

	$start_n=$ip1*16777216+$ip2*65536+$ip3*256+$ip4;
	$end_n=$ip5*16777216+$ip6*65536+$ip7*256+$ip8;

	for ($nip=$start_n; $nip<=$end_n; $nip++) {
		$oct1=int($nip/16777216);
		$var1=$nip%16777216;
		$oct2=int($var1/65536);
		$var2=$var1%65536;
		$oct3=int($var2/256);
		$oct4=$var2%256;
		# Skip Network and Broadcast addresses
		next if ( $oct4 == 0 or $oct4 == 255 );
		$scanip="$oct1.$oct2.$oct3.$oct4";
		# Insert individual IP's into nessus_scan
		$sth_check=$dbh->prepare(qq{
			SELECT count(*) from nessus_scan
			WHERE sched_id=$sid
			AND host="$scanip"
			});
		$sth_check->execute;
		($is_submitted)=$sth_check->fetchrow_array;
		$sth_check->finish;
		if($is_submitted==0) {
			$sth_ins_nessus_scan->execute($sid, $scanip, "S");
		}
	}
	$sth_ins_nessus_scan->finish;

}



#Select # of available scans to run

my($available_server_rsc);
$sth_sel=$dbh->prepare(qq{
	SELECT COUNT(max_scans-current_scans)
	FROM nessus_servers
	WHERE checkin_time+300>now()
	AND max_scans>current_scans
	});
$sth_sel->execute;
($available_server_rsc)=$sth_sel->fetchrow_array;

if ($available_server_rsc>0) {
	$sth_sel=$dbh->prepare(qq{
		SELECT SUM(max_scans-current_scans)
		FROM nessus_servers
		WHERE checkin_time+300>now()
		AND max_scans>current_scans
		});
	$sth_sel->execute;
	($available_server_rsc)=$sth_sel->fetchrow_array;
}
else {
	$available_server_rsc = 0;
}

#Select number of scheduled scans to run
$sth_sel=$dbh->prepare(qq{
	select count(*)
	from nessus_scan
	where status='S' and server_id=0
	});
$sth_sel->execute;
my($number_of_ips_to_scan);
($number_of_ips_to_scan)=$sth_sel->fetchrow_array;


#Get minimal value of two (available server scans and actual scans to perform)
my($number_of_scans);
$number_of_scans=$available_server_rsc;

if($number_of_scans>$number_of_ips_to_scan) {
	$number_of_scans=$number_of_ips_to_scan;
}
if ($number_of_scans>0) {
$sth_sel=$dbh->prepare(qq{
	select t1.host, t1.sched_id, t2.username
	from nessus_scan t1, nessus_schedule t2
	where t1.status="S" and t1.server_id=0
	AND t2.id=t1.sched_id
	order by submit_dttm
	limit $number_of_scans
	});
$sth_sel->execute;

my($sth_sel1,$serv_id, $user_name);

while (my($current_ip, $sched_id, $user_name)=$sth_sel->fetchrow_array) {
	$sth_sel1=$dbh->prepare(qq{
		SELECT t3.nessusid
		FROM nessus_zone_ranges t2, nessus_server_zones t3, nessus_servers t4, nessus_user_zones t5
		WHERE inet_aton( "$current_ip" ) >= t2.range_start
		AND inet_aton( "$current_ip" ) <= t2.range_end
		AND t3.zoneid = t2.zoneid
		AND t3.nessusid=t4.id
		AND t4.current_scans<t4.max_scans
		AND t4.checkin_time+300>now()
		AND t5.zid=t3.zoneid
		AND t5.username="$user_name"
		ORDER by t4.max_scans-t4.current_scans DESC
		limit 1
		});
	$sth_sel1->execute;



	($serv_id)=$sth_sel1->fetchrow_array;

	print localtime(time)." sched: servid=$serv_id\n";


	if($serv_id>0) {
		$sth_upd=$dbh->prepare(qq{
			update nessus_scan
			set server_id=$serv_id
			where host="$current_ip"
			and sched_id=$sched_id
			});
		$sth_upd->execute;
		$sth_upd=$dbh->prepare(qq{
			update nessus_servers
			set current_scans=current_scans+1
			where id=$serv_id
		});
		$sth_upd->execute;
		$number_of_scans=$number_of_scans-1;


		}

	}
	$sth_sel1->finish;
}
$sth_sel->finish;
$sth->finish;
$sth_upd->finish;

#Close DB connection
$dbh->disconnect or die("Failed to disconnect : $DBI::errstr\n");

print localtime(time)." sched: Finish\n";
close LOG;

exit 0;



sub schedule_scan {
	my ($schedid, $username, $schedule_type, $stime, $day_of_week, $day_of_month) =@_;
my ($dy, $dm, $dd);
$dy=0;
$dm=0;
$dd=0;



if ($schedule_type eq "N") {


}

elsif ($schedule_type eq "D") {
	if ($stime<$timenow) {
		#Update next scan datetime
		$dd=1;
	};
}
elsif ($schedule_type eq "W") {

	if ($day_of_week<$wday) {
		$dd=7-$wday+$day_of_week;
	}
	elsif ($day_of_week==$wday && $stime<$timenow) {
		$dd=7;
	}
	elsif ($day_of_week>$wday) {
		$dd=$day_of_week-$wday;
	};



}
elsif ($schedule_type eq "M") {

	#Getting max numbers of days in the month
	my ($daysm, $mydom);
	$mydom=$day_of_month;
	$daysm=Days_in_Month($year, $mon);


	my ($nyear, $nmon);
	if ($mon<12) {
		$nmon=$mon+1;
		$nyear=$year;
	}
	else {
	$nmon=1;
	$nyear=$year+1;
	};

	my ($daysnm);
	$daysnm=Days_in_Month($nyear, $nmon);



	if ($daysm < $day_of_month) {
		$mydom=$daysm;
	};


	if ($mydom < $mday) {
		$dd=$daysm-$mday;
		if ($day_of_month gt $daysnm) {
			$dd=$dd+$daysnm;
		}
		else {
			$dd=$dd+$day_of_month;
		};
	}
	elsif ($mydom == $mday && $stime lt $timenow){
		$dd=$daysm-$mday;
		if ($day_of_month gt $daysnm) {
			$dd=$dd+$daysnm;
		}
		else {
			$dd=$dd+$day_of_month;
		};
	}
	elsif ($mydom > $mday) {
		$dd=$mydom-$mday;
	};
};
my $nextscan;

if ($schedule_type eq "N") {
	$nextscan = "$now";
}
else {
my ($syear, $smonth, $sday)=Add_Delta_YMD($year,$mon,$mday,$dy,$dm,$dd);


if ($smonth<10) {
	$smonth="0".$smonth;
};

if ($sday<10) {
	$sday="0".$sday;
};

$nextscan = "$syear$smonth$sday$stime";
}

$sth_upd = $dbh->prepare(qq{
	UPDATE nessus_schedule
	SET next_scan='$nextscan', status='S'
	WHERE username='$username' and id=$schedid
	} );
$sth_upd->execute;

}

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)." sched: No inprotect.cfg 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 email_results {

	my ( $email_addr, $username, $scantime, $dbhf, $schedid, $fromemail, $resulturl) = @_;
	my ( $msg, $html_data, $sth_sel );
	$sth_sel=$dbh->prepare(qq{
		SELECT COUNT(DISTINCT host) FROM nessus_results
		WHERE scantime='$scantime' AND username='$username' AND sched_id=$schedid
		} );
	$sth_sel->execute;
	my ($hostcount)=$sth_sel->fetchrow_array;
	$sth_sel=$dbh->prepare(qq{
	SELECT COUNT(risk) AS rcount, risk FROM nessus_results
		WHERE scantime='$scantime' AND sched_id=$schedid AND username='$username' GROUP BY risk
		} );
	$sth_sel->execute;

	my $riskres = "";
	my $riskcounttotal = 0;

	while (my($riskcount, $risklevel)= $sth_sel->fetchrow_array) {
		$riskres=$riskres."Risk Level: $risklevel - $riskcount\n";
		$riskcounttotal=$riskcounttotal+$riskcount;
	}

	$sth_sel=$dbh->prepare(qq{
		SELECT count(distinct scantime) as scount FROM nessus_results
		where sched_id=$schedid and username='$username'
		} );
	$sth_sel->execute;

	my($scancount)=$sth_sel->fetchrow_array;

	my $prevscanmsg = "";


	if ($scancount>1) {
		$sth_sel=$dbh->prepare(qq{
			select distinct scantime as prevscan from nessus_results
			where sched_id=$schedid and username='$username' order by scantime desc Limit 1,1
			} );
		$sth_sel->execute;
		my($prevscan)=$sth_sel->fetchrow_array;
		$sth_sel=$dbh->prepare(qq{
			Select count(distinct host) from nessus_results
			where scantime='$prevscan' and username='$username' and sched_id=$schedid
			} );
		$sth_sel->execute;
		my ($prevhostcount)=$sth_sel->fetchrow_array;
		$sth_sel=$dbh->prepare(qq{
			SELECT count(risk) as rcount, risk FROM nessus_results
			where scantime='$prevscan' and sched_id=$schedid and username='$username' group by risk
			} );
		$sth_sel->execute;

		my $prevriskres = "";
		my $prevriskcounttotal = 0;

		while (my($prevriskcount, $prevrisklevel)= $sth_sel->fetchrow_array) {
			$prevriskres=$prevriskres."Risk Level: $prevrisklevel - $prevriskcount\n";
			$prevriskcounttotal=$prevriskcounttotal+$prevriskcount;
		}
		$prevscanmsg="Previous scan summary:
Scanned $prevhostcount system(s)

Vulnerabilities found - $prevriskcounttotal
$prevriskres";
	}



	$msg = MIME::Lite->new(
		To      =>"$email_addr",
		From    =>"$fromemail",
		Subject =>"$CONFIG{'EMAILSUBJECT'}",
		Data    =>"Dear $username, \n\nScheduled vulnerability scan has finished. You can access the scan results here: \n$resulturl/results.php?scantime=$scantime&schedid=$schedid&scantype=A\n\n
Scan summary:
Scanned $hostcount system(s)

Vulnerabilities found - $riskcounttotal
$riskres

$prevscanmsg

This report has been prepared by Inprotect software - web interface for Nessus security scanner.\nPlease visit Inprotect.com for an updated version of the software.
"
	);
	$msg->send();

}

sub scan_started_email {
	my ( $email_addr, $username, $scantime) = @_;
	my ( $msg, $html_data );
	$msg = MIME::Lite->new(
		To      =>"$email_addr",
		From    =>"$fromemail",
		Subject =>"Vulnerability scan started.",
		Data    =>"Dear $username,\n\nScheduled vulnerability scan has started."
		);
	$msg->send();
}
