use strict; use warnings; use diagnostics; use POSIX qw(setsid); use DBI; use Config::Simple '-strict'; use Qualys; use vars qw($hup); use Class::Date qw(:errors date localdate gmdate now -DateParse -EnvC); use Net::SMTP; use Config::Simple '-strict'; ################################################################################ # Qualys API Daemon # Written By: Eric Boersma # Date: 7/14/2009 # # Function: This daemon is the API call handler for the Qualys API. Due to # restrictions placed on the API by Qualys, this system handles desired # API calls in a timely fashion. By daemonizing this process, we ensure # that the maximum number of API calls (300) happens in a 24-hour period. # This loop opens the priority modified FIFO (First In First Out) queue in # the MySQL database, reads the highest priority, oldest entry, # determines the type of API call to be made, and hands that request off # to a subroutine to do the heavy lifting. Once the subroutine is finished # the daemon waits five minutes before starting the next call, so as not # to exceed the maximum number of API function calls in a 24-hour period. # # Usage: In order to interface with this Daemon, send your desired API call to # the database table APIQueue in the designated format. ################################################################################ ################################################################################ # # Preparing our Subroutines # ################################################################################ sub processCall{ my $conf = $-[1]; my $call = $_[0]; my $qualys = new Qualys; #Instantiate Qualys my $uname = $conf->param("qualysuname"); my $pass = $conf->param("qualyspass"); #Log in $qualys->userid($uname); $qualys->passwd($pass); #Connect to the requested script, using our modified format. #$call should just be a querystring with the api function call as the start. return $qualys->connect_to($call); } #This script is run on program exit, it is called after errorOut() #If errorOut() has already printed an error, we won't print another one sub programExit{ print "Exiting program!\n"; my $progname = $0; #program name my $now = date now; #right now my $exit = $?; #exit error code my $log; #prepare the line to be written to the log if($exit == 7142){ open (LOG, ">>/some/directory/APIlog") or die "$!"; #open the log file print LOG "The program successfully began execution at $now\n"; close LOG; return; } open (LOG, ">>/some/directory/APIlog") or die "$!"; #open the log file $log = "$now Qualys API Queue daemon exited unexepectedly. "; #standard statement if($exit != 0){ $log = $log . "The program exited with errors\n The error was: $!"; #append additional information } else{ $log = $log . "The program exited without errors\n"; #ditto } if($exit != 5432){ print LOG $log; #if this isn't a code provided by errorOut(), then print the log } SendMail($log, "$progname terminated unexpectedly", "failmail"); #no matter what, tell the programmer about the exit close LOG; #cleanliness } sub SendMail{ #standard SendMail() sub which you've probably seen before if you've edited any of my other code. #Don't really feel necessary to go into the inner workings, it's pretty straightforward my $report = $_[0]; my $subject = $_[1]; my $list = $_[2]; my $conf = new Config::Simple('bind.conf') or die "Can't find config file"; my @email = $conf->param($list); my $smtp = Net::SMTP->new( Host => 'mail.company.com', Hello => 'deptname.company.com', Timeout => 30, Debug => 0, ); $smtp->mail('deptname@company.com'); foreach my $email (@email) { $smtp->to($email); } $smtp->data(); $smtp->datasend("From: Qualys API Daemon\n"); foreach my $email (@email) { $smtp->datasend("To: $email\n"); } $smtp->datasend("Subject: $subject\n"); $smtp->datasend("MIME-Version: 1.0\n"); $smtp->datasend("Content-type: text/html\n"); $smtp->datasend("Content-Transfer-Encoding: 7bit\n"); $smtp->datasend("\n"); $smtp->datasend("$report\n"); $smtp->dataend(""); $smtp->quit(); } #Captures the $! error input in placing of dying the program, and prints it to the log. sub errorOut{ my $error = $_[0]; open (LOG, ">>/some/directory/APIlog") or die "$!"; print LOG "$error\n"; close LOG; exit 5432; } sub handleErorr{ #TODO: Add additional error handling for multiple types of erros here my $error = $_[0]; open (LOG, ">>/some/directory/APIlog"); print LOG "$error\n"; close LOG; } sub dailyTasks{ my $currentfile = $_[0]; my $date = date now; my @day = split(/ /, $date); } $| = 1; #Signal handling variables for HUP restarts. $hup = 0; #if we get a kill -hup call, increment the hup counter so the loop knows to restart $SIG{HUP} = sub{ $hup++; }; ################################################################################ # # public static void main() # ################################################################################ #Due to the looping nature of this program, any configuration changes will require #a service restart. This is by design. open(LOG, ">>/some/directory/APIlog") or errorOut("$!"); #We will print to a log file chdir '/some/directory' or errorOut("Can't change directory: $!"); #set our root umask 0; #mask ourselves w/ userid 0, so the calling user isn't tied to us open (STDIN, "/dev/null") or errorOut("$!"); #no STDIN for this service open (STDOUT, ">/dev/null") or errorOut("$!"); #Or STDOUT open (STDERR, ">/dev/null") or errorOut("$!"); #or STDERR defined(my $pid = fork) or errorOut("Can't fork: $!"); #fork the process to create a child exit 7142 if $pid; #the fork makes it so that we're not tied to the user's shell session setsid or errorOut("Can't start a new session: $!"); #start a new session #Config files to Log us into the database my $conf = new Config::Simple('/some/directory/conf') or errorOut("Can't find config file"); my $username = $conf->param("db_username"); my $password = $conf->param("db_password"); my $dbh = DBI->connect("dbi:mysql:database=DBName; host=server.company.com; port=3306", $username, $password,) or errorOut(DBI->errstr); my $now = date now; open (my $currentfile, ">", "/some/directory/CurrentLog"); while(1){ #This while loop will never turn false, so the service is designed to always run. my $timer = $now + '1D'; my $report = date now; if($report >= $timer){ dailyTasks($currentfile); $now = $report; } my $sql = "SELECT RequestTime, Priority, RequestString, OutputFile, OutputScript, QueueID FROM APIQueue ORDER BY Priority, RequestTime LIMIT 1"; #get our next call to process my $sth = $dbh->prepare($sql); my $sql2 = "DELETE FROM APIQueue WHERE QueueID = ?"; my $sth2 = $dbh->prepare($sql2); $sth->execute(); while(my @row = $sth->fetchrow_array()){ #assign the relevant values my $time = $row[0]; my $priority = $row[1]; my $request = $row[2]; my $output = $row[3]; my $script = $row[4]; my $id = $row[5]; open(OUTPUT, $output) or errorOut("Couldn't open output buffer $output: $!"); my $result = processCall($request, $conf); #make the call print OUTPUT $result; #print the result to the requested file close OUTPUT; my $parser; #check which file parser we're going to be using, based on a regex in the script name. if($script =~ m/\.php/i){ $parser = "php"; } if($script =~ m/\.pl/i){ $parser = "perl"; } if(!($pid = fork)){ #Run the child process to process the output. system("$parser $script"); } $sth2->execute($id); #Delete the record my $log = "ID: $id, Requested: $time, Priority: $priority, Call: $request, Processed: $report\n"; print LOG $log; #TODO: schedule nightly logging and result emails #TODO: Exception handling. } #check our hup, and restart the program with default arguments if we get a hup signal. if ($hup > 0){ SendMail("Qualys API Daemon restarted successfully", "Qualys API Daemon Restart", "failmail"); exec($0, @ARGV) || warn "$!"; } sleep(300); } END{ programExit(); }