#!/usr/bin/perl
# RCS: $Header: /home/root/bin/RCS/chklogs,v 1.7 1995/12/20 20:38:57 root Rel $
# AUTHOR: D. Emilio Grimaldo T.		root@panama.iaehv.nl
# USAGE: chklogs [[-t | -c] | [-w | -a | -m]
# DESCRIPTION:
#          Checks the sizes of system Log files and reports the results.
#	   The full pathname of the log(s) together with the maximum
#	allowed size and default action are specified in `logf'.
#	   Default actions can be `archive' or `truncate'. When
#	archiving a shuffling mechanism is used to save disk space.
#       
#   Options:
#	none	Process logs according to default action 
#	-a	Archive all overgrown logs regardless of default action
#	-m	Mail summary to administrator
#	-c	Only produce a listing of the (archived) logs
#	-t	Check correctness of Index indicating registered action
#	-w	Warn about overgrown logs without taking action
#
#   NOTE: mail is sent to admin ONLY if one or more logs are overgrown
$logf="/etc/chklogs.conf";		# Log Index File
$zipper="/bin/gzip";			# Compress program 
$zipext="gz";				# Extension given by zipper command
$mailout="/tmp/chklogs.out";		# Temporary file for -m option
$mailer="/usr/sbin/sendmail -ep -i ";
$admin="root";				# Administrator, gets mail
$mailcmd="$mailer $admin"; 		# Command for mail option
$maxlogs=5;				# Shuffle after maxlogs archives
$a_archive="archive";			# Constant for ARCHIVE action
$a_truncate="truncate";			# Constant for TRUNCATE action
$a_execute="execute";			# Constant for EXECUTE action
$syslogF="/var/run/syslog.pid";		# Location of syslog.pid file
$VERSION="** c h k l o g s  V 1.7 **";

#
# PROCESS LOG
#    Processes the log according to it's associated action. Every
#    log can be processed according to `archive' or `truncate'
#   WARN	Only warn administrator that this log is too big
#   ARCHIVE	Archive log, check if overriding truncate from command line
#   TRUNCATE	Truncate the log if it is the default action 
#   EXECUTE	Execute external program, parameter is /path/to/log 
#
sub process_log {
   local($logname, $p_action) = @_;

    if ( $size > $f_size ) { 			# Has it grown too much?
	$notify = 1;				# Mail ONLY IF at least one is too big
	if (defined($opt_warn)) {		# Warn only!
	    $p_action = $p_action . "!";
	    &console_output($p_action);
	}
	if (defined($opt_archive) ||		# Override default action!
	    $p_action eq $a_archive) { 		# Default: archive
	    &shuffle_logs($logname);
	    &archive_log($logname);
	}
	elsif ($p_action eq $a_execute) {	# Execute external program
	    system("$exec_pgm $logname");
	    &console_output($p_action);
	}
	elsif ($p_action eq $a_truncate) {	# Default: truncate
#	    &shuffle_logs($logname);
	    &truncate_log($logname);
	}
    }
    else {					# Size is within limits
	&console_output("   ok");		# Size is ok
    }
}

#
# Archives the log by compressing it and adding a timestamp in
# the filename. No new log is created if it already exists.
#
sub archive_log {
    local($logname) = @_;
    local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
    local($archiveName,$zipArchive);

    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
    $ArchiveSuffix = sprintf(".%2d%02d%02d",$year,$mon+1,$mday);
    $archiveName = $logname . $ArchiveSuffix;
    $zipArchive = $archiveName . ".$zipext";

    if ( ! -e $zipArchive) {
	if (! system("$zipper < $logname > $zipArchive && : > $logname")) {
	    chown $UID,$GID,$zipArchive;	# Set correct ownership
	    chmod $mode, $zipArchive;		# Set correct permissions
	    &console_output("archived");	
	}
	else {
	    warn "Could not create $zipArchive\n";
	    &console_output("archive!");	# give a warning
	}    
    }
    else {
	&console_output("archive!");		# give a warning
    }
}

# TRUNCATE_LOG
#	Truncates the log to zero length using system call 
sub truncate_log {
    local($logname) = @_;
    truncate($logname,0);
    &console_output("truncated");
}

# CONSOLE_OUTPUT
#	Output to either console or mail file
sub console_output {
    local($act) = @_;

    if (defined($opt_mail)) {
    	printf TMP "%-50s %7d   %7d %s\n",$f_name,$size,$f_size,$act;
    }
    else {
      	printf "%-50s %7d   %7d %s\n",$f_name,$size,$f_size,$act;
    }
}

#
# PREPARE_HEADER Sets up the header to be displayed or mailed out
# 		 
sub prepare_header {

    if (defined($opt_mail)) {
	open(TMP,"> $mailout") || die "chklogs: Could not create mail file";
	print  TMP "Subject: System logs (chklogs)\n";
	print  TMP "\t\t\t$VERSION\n\n";
	printf TMP "System Logs Index: %s    Sizes: bytes\n", $logf;
	printf TMP "%-50s Current - Allowed  Action\n","Log Name";
	print TMP "-" x 50," ","-" x 7,"   ","-" x 7,"  ","-" x 6,"\n";
	$notify = 0;
    }
    elsif (!defined($opt_check)) {
	printf "System Logs Index: %s    Sizes: bytes\n", $logf;
	if (defined($opt_test)) {
	    printf "%-50s Allowed   Action  Arch.\n","Log Name";
	    print "-" x 50," ","-" x 7,"   ","-" x 6,"  ","-----\n";
	}
	else {
	    printf "%-50s Current - Allowed  Action\n","Log Name";
	    print "-" x 50," ","-" x 7,"   ","-" x 7,"  ","-" x 6,"\n";
	}
    }
}

sub basename {
    local($fpath) = @_;
    $fpath =~ s/[a-zA-Z0-9_.\/-]*\///;
    return $fpath;
}

sub dirname {
    local($fpath) = @_;
    $fpath =~ s/[a-zA-Z0-9._-]*$//;
    return $fpath;
}
#
# Lists all logs matching the name, including a full path 
#
sub list_logs {
    local($fullpath, $default_action) = @_;

    $dname = &dirname($fullpath);
    $bname = &basename($fullpath);
    chdir($dname);
    print "\nRegistered $bname(s) in: $dname  [$default_action]\n";
    system("/bin/ls $bname*  2> /dev/null");
}
#
# Shuffles logs, when the maximum number of archived logs (per log name)
# is reached we remove the oldest so that we don't waste disk space with
# too many archived logs
#
sub shuffle_logs {
    local($fullpath) = @_;
    local($logcnt,@LogList);

    $logcnt = 0;
    $dname = &dirname($fullpath);
    $bname = &basename($fullpath);
    chdir($dname);
    open(ARCHLIST,"ls $bname.*.$zipext 2> /dev/null |") || warn "Could not pipe shuffle";
    while (<ARCHLIST>) {
	chop($_);
    	@LogList[$logcnt] = $_;
	$logcnt += 1;
    }
    close(ARCHLIST);
    if ($logcnt >= $f_max) {
	unlink(@LogList[0]);		# Remove oldest archived log
    }
}

# STOP_PROCESS(path_of_pid_file)
sub stop_process {
    local($pid_file) = @_;
    local($itsPID);
    open(PID,"$pid_file");
    read(PID,$itsPID,10);
    kill 'STOP', $itsPID;
    close(PID);
    return $itsPID;
}

# CONT_PROCESS(pid)
sub cont_process {
    local($itsPID) = @_;
    kill 'CONT', $itsPID;
}

#
# MAIN
#
if ($#ARGV == -1) {
    $opt_default = 1;
}
else {
    foreach $i (0 .. $#ARGV) {
	if ($ARGV[$i] eq "-a") { $opt_archive = 1; }
	if ($ARGV[$i] eq "-m") { $opt_mail = 1; }
	if ($ARGV[$i] eq "-c") { $opt_check = 1; }
	if ($ARGV[$i] eq "-t") { $opt_test = 1; }
	if ($ARGV[$i] eq "-w") { $opt_warn = 1; }
    }
}
open(logfile,$logf) || die "Cannot open Log Index\n";
&prepare_header();
$pid = &stop_process($syslogF);		# send SIGSTOP to syslogd
					# Add httpd if you have it
while (<logfile>) {
    if (/^#/) {				# Comments start with `#'
	next;
    }
#   Logname   MaximumSize  DefaultAction MaxNrLogs
    ($f_name, $f_size, $f_action, $f_max) = split(' ');

#   External handler?
    undef($exec_pgm);
    if ($f_action eq $a_execute) {
	$exec_pgm = $f_max;
    }
#   Use the default minimum if not specified in index file
    if ($f_max == 0) { $f_max = $maxlogs; }

    ($dev,$ino,$mode,$nlink,$UID,$GID,$rdev,$size,$atime,
     $mtime,$ctime,$blksize,$blocks) = stat($f_name); # Logfile stats.

#
#   Check if the Action field is valid: `archive' or `truncate'
#
    if ($f_action ne $a_archive && $f_action ne $a_truncate &&
        $f_action ne $a_execute) {
	$f_action = "????";
    }
#
#   Now do whatever we want with the logs
#   ARCHIVE option overrides TRUNCATE if given in the command line (-a)
#
    if (defined($opt_mail)    || defined($opt_default) || 
	defined($opt_archive) || defined($opt_warn)) {
	&process_log($f_name, $f_action);
    }
    elsif (defined($opt_check)) {     	# Just list the archives and logs
	&list_logs($f_name,$f_action);
    }
    elsif (defined($opt_test)) {      	# Sanity check of index file
	if ($f_action eq $a_execute) {
    	    printf "%-50s %+7s   execute %s\n", $f_name, $f_size, $exec_pgm;
	}
	elsif ($f_action eq $a_truncate) {
    	    printf "%-50s %+7s   truncate -\n",$f_name,$f_size;
	}
	else {
    	    printf "%-50s %+7s   %-8s %d\n",$f_name,$f_size,$f_action,$f_max;
	}
    }
}
&cont_process($pid);			# send SIGCONT to syslogd
					# add for httpd if you have it
#
#   We are almost done...
#
close(logfile);
if (defined($opt_mail)) {
    print TMP "\nArchives as <logname>$ArchiveSuffix.$zipext\n";
    close(TMP);
    system("cat $mailout | $mailcmd") if ($notify);
    unlink($mailout);
}
else {
    print "\nArchives as <logname>$ArchiveSuffix.$zipext\n" if (defined($opt_default));
}
