#!/usr/bin/perl
# $Id: chklogsadm,v 1.3.2 1997/09/28 19:39:42 grimaldo Exp $
#------------------------------------------------------------------------
#       chklogsadm (c)1996,1997 D. Emilio Grimaldo Tunon
#------------------------------------------------------------------------
# AUTHOR: D. Emilio Grimaldo T.         grimaldo@panama.iaehv.nl
# DESCRIPTION:
#	The ChkLogs Administrative script. Due to the increased complexity
#	of ChkLogs, it has been necessary to partition the system into the
#	main processing script (chklogs) and the administrative & maintenance
#	script (chklogsadm). Common code is shared via the library.
# USAGE: 
#	newconf  {-global GLOBAL_REPOSITORY | -local} [-force]
#	newgroup  -g GROUP -b PREprocessingCmd -a POSTprocessingCmd [-m]
#	gadd	  -g GROUP -l LOG -s SIZE/AGE { -a qty | -t | -e WHAT }
#	init		
#	initrepos
#	rmgroup   -g GROUP
#	del	  -l LOG   -t { A | T | E }
#	sync
#	when	  LOG [ A | T | E ]
#	syslog

# ********* CONFIGURATION SECTION *********
use	lib '/usr/local/lib/chklogs';
# ********* ********************* *********

use 	Getopt::Long;
use	Chklogs;
use	Interpret;
use	strict;

# ********** LOCAL DATA SECTION **********
my ($DebugLevel, $opVersion, $opPre, $opPost, $opGroup, $opModify );
my ($opLog, $opSize, $opTruncate, $opExecute, $opArchive, $opType );
my ($opVerbose);
my ($Repository, $Service, $cvsId);
my (%cLog, %cGroup, %cfg);
# ********* ********************* *********

$cvsId = '$Revision: 1.3.2 $';

sub Usage {
    print "\nUsage: chklogsadm @_\n";
    exit 1;
}

sub VerboseAbort {
    print "ChklogsAdm: @_\n" if ($opVerbose);
    exit 2;
}

#-----------------------------------------
# GetConfigVars
#       Get any configuration variables we might need

sub GetConfigVars {
    $DebugLevel = $ENV{'DEBUG'};
}

sub initialize {
    $cvsId =~ m/Revision:\s+(\d+\.\d+\.*\d*\.*\d*)/;
    $cvsId = $1;

    %cfg = (
            'global' => "",                     # Alternate Repository Feature
	    'local'  => $RelativePath,
	    'reptype'=> "",                     # Repository Type (global|local)
	    'options'=> ""
	   ); 
    %cLog = (
            'name'      => "",                  # Fully qualified name
	    'size'      => 0,                   # Current file size (stat)
	    'mode'      => 0640,                # Current file mode (stat)
	    'uid'       => 0,                   # File Owner user (stat)
	    'gid'       => 0,                   # File Owner group (stat)
	    'threshold' => 0,
	    'action'    => "",
	    'max'       => 0,
	    'params'    => ""
	    );
    %cGroup = (
              'name'      => 'common',
	      'pre'       => '',
	      'post'      => ''
	      );
}

#-------------------------------
# FixPermissions
#

sub FixPermissions {
    my $fn  = shift;

#    ($dev,$ino,$mode,$nlink,$UID,$GID,$rdev,$size,$atime,
#     $mtime,$ctime,$blksize,$blocks) = stat($fn);
    chmod 0640, $fn;
#    chown $UID,$GID,$fn;
}


#-------------------------------
# BackupFileAndRefresh(originalName,Current)
#	The original file is backed up with the	.old suffix and 
#	the Current is renamed to the original name.
#	Permissions are kept on both.

sub BackupFileAndRefresh {		# PROTO($,$)
     my($Original,$New) = @_;

     if ($DebugLevel == 0) {
	 xdevRename($Original,"$Original.old");
	 xdevRename($New,$Original);
     }	 
     &FixPermissions($Original);
     &FixPermissions("$Original.old");
}

sub MakeNewAgeEntry {			# PROTO(\*,$,$)
    my($dbFile, $LogPath, $Action) = @_;
    my($day, $month, $year);

    $Action = "A" if ($Action eq 'archive');
    $Action = "T" if ($Action eq 'truncate');
    $Action = "E" if ($Action eq 'execute');
    ($day,$month,$year) = unpack('C A3 I',&GetCurrentDate);
    printf $dbFile "%d %s %d  %s  %s\n",$day,$month,$year,$Action,$LogPath;
}


#-------------------------------
# InitAgeLog() :: init
#	Makes a *new* resource file out of configuration
#
sub InitAgeLog {
    my($sz,$act,$f_name,$rest);

    open(CONF,$ConfFile) || die "Cannot open $ConfFile\n";
    open(RC,"> $ResrcFile");

    while (<CONF>) {
        chop;
        next if (/^#/ || /^\s*$/ || /^-/);             # ignore comment line
        ($f_name, $sz, $act, $rest) = split(" ",$_,4);

        &MakeNewAgeEntry(\*RC, $f_name, $act) if ($f_name ne "");
    }
    close(CONF);
    close(RC);
    &FixPermissions($ResrcFile);
}

#-------------------------------
# SyncAgeLog() :: sync
#	Resynchronizes the Age Resource file to 
#	the current state of the configuration file
#	so that we don't have to scan the whole
#	array for a hit.
#
sub SyncAgeLog {
    my($scnt,$icnt,$Action,$sz,$rest,$f_name,$Previous);
    my($tDay,$tMon,$tYear,$Today,$NewSync);
    my(@ConfLogsAction, @ConfLogs, @ConfLogsDate);

    chdir(&dirname($ResrcFile));
    ($tDay,$tMon,$tYear) = unpack('C A3 I',&GetCurrentDate);
    $Today = "$tDay $tMon $tYear";      # For those without previous history

    open(CONF,$ConfFile) || die "Cannot open $ConfFile\n";

    $scnt = 0;
    while (<CONF>) {
        chop;
        next if (/^#/ || /^\s*$/ || /^-/);          # ignore comment line
        ($f_name, $sz, $Action, $rest) = split(" ",$_,4);
        $Action = "A" if ($Action eq 'archive');
        $Action = "T" if ($Action eq 'truncate');
        $Action = "E" if ($Action eq 'execute');

        $ConfLogs[$scnt] = $f_name;
        $ConfLogsAction[$scnt] = $Action;
        $scnt += 1;
    }
    close(CONF);

    &ReadTimeLog;                           # Read in memory cache

    $NewSync = $ENV{'HOME'} . "/chklogs.sync";
    open(NRC,"> $NewSync");
    $icnt = 0;
    while ($icnt < $scnt) {
        $Previous = &TimeLogStamp($ConfLogs[$icnt],$ConfLogsAction[$icnt]);
	# We are not going to use ModifyTimeLog because we need to
	# change the ordering of the cached age log. WriteTimeLog not needed.
        if ($Previous ne "") {
            $ConfLogsDate[$icnt] = $Previous;
        }
        else {
            # No history, must be a new one, give it today's date
            $ConfLogsDate[$icnt] = $Today;
        }
        # Now make entry into new file
        printf NRC "%s  %s  %s\n", $ConfLogsDate[$icnt],
                                   $ConfLogsAction[$icnt],
                                   $ConfLogs[$icnt];
        $icnt += 1;
    }
    close(NRC);
    unlink($ResrcFile);
    $icnt = xdevRename($NewSync,$ResrcFile);
    print "SyncAgeLog: xdevRename failed $!\n" if ($icnt != 0);
    &FixPermissions($ResrcFile);
}


#-------------------------------
# AddGroup() :: newgroup
#	Adds a new group to the configuration
#
sub AddGroup {
    my($Group, $GroupFound, $NewConf);

    &GetOptions("v"	=> \$opVerbose,
    		"g=s"	=> \$opGroup,
		"b=s"	=> \$opPre,
		"a=s"	=> \$opPost,
		"m"	=> \$opModify);

    if ($opGroup eq 'common') {
	&VerboseAbort("AddGroup - reserved group name!\n");
    }
    if (defined($opGroup) && defined($opPre) && defined($opPost)) {
	$GroupFound = 0;
	$NewConf = $ENV{'HOME'} . "/" . &basename($ConfFile) . ".new";
	$NewConf = '-' if $DebugLevel > 0;	# Use stdout instead

	open(CONF,$ConfFile) || die "cannot open configuration\n";
	open(NEW,">$NewConf") || die "cannot create new conf.\n";

	while(<CONF>) {
	    if (/^#:group\s+/i  || /^-group\s+/i) {
		$Group = $';
		chop($Group);
		$Group =~ s/\s*//g;
		if ($Group eq $opGroup) {
		    if ( ! defined($opModify) ) {	# Not modifying
			$GroupFound = 1;
			last;			# Error condition
		    }
		    else {
			$GroupFound = 2;
			print NEW "-Group $opGroup\n";
			print NEW "-Pre $opPre\n-Post $opPost\n";
			<CONF>;			# Consume Pre
			<CONF>;			# Consume Post
			next;
		    }
		}		
	    }
	    print NEW $_;
	}

	close(CONF);

	if ($GroupFound == 1) {
	    close(NEW);
	    unlink($NewConf) if $DebugLevel == 0;	# discard tmp file
	    &VerboseAbort("AddGroup - $opGroup already exists!\n") 
	}

	if ($GroupFound == 0) {
	    print NEW "-Group $opGroup\n";
	    print NEW "-Pre $opPre\n-Post $opPost\n\n";
	    print NEW "# ENDGROUP\n";
	}
	close(NEW);
	&BackupFileAndRefresh($ConfFile,$NewConf);
    }
    else {
    	&Usage("newgroup -g GROUP -b PRE -a POST [-m]\n");
    }	
}

#-------------------------------
# RmGroup() :: rmgroup
#	Removes a group header from the configuration
#
sub RmGroup {
    my($Group,$GroupFound,$NewConf);

    &GetOptions("v"	=> \$opVerbose,
    		"g=s"	=> \$opGroup);

    if ($opGroup eq "common") {
	&VerboseAbort("RmGroup - reserved group name!\n");
    }

    if (defined($opGroup)) {
	$GroupFound = 0;
	$NewConf = $ENV{'HOME'} . "/" . &basename($ConfFile) . ".new";
	$NewConf = "-" if $DebugLevel > 0;	# Use stdout instead

	open(CONF,$ConfFile) || die "cannot open configuration\n";
	open(NEW,">$NewConf") || die "cannot create new conf.\n";

	while(<CONF>) {
	    if (/^#:group\s+/i  || /^-group\s+/i) {
		$Group = $';
		chop($Group);
		$Group =~ s/\s*//g;
		if ($Group eq $opGroup) {
			$GroupFound = 1;
			<CONF>;			# Consume Pre
			<CONF>;			# Consume Post
			next;
		}		
	    }
	    if (/^#\s*ENDGROUP/i) {
		if ($GroupFound > 0) {
		    $GroupFound = 0;
		    print NEW "\n";
		    next;
		}
	    }
	    print NEW $_;
	}

	close(CONF);
	close(NEW);

	&BackupFileAndRefresh($ConfFile,$NewConf);
    }
    else {
    	&Usage("rmgroup -g GROUP\n");
    }	
}

#-------------------------------
# AddToGroup() :: gadd
#	Adds a log to a configured group in the configuration.
#	The reserved group 'common' is used for all other logs,
#	in this case we append to the end of the file.
#
sub makeRecord {
    my($rcd, $action);

    $action = "archive $opArchive" if defined($opArchive);
    $action = 'truncate' if defined($opTruncate);
    $action = "execute $opExecute" if defined($opExecute);
    $rcd = "$opLog \t\t$opSize    $action\n";
    return $rcd;
}

sub AddToGroup {
    my($Group,$GroupFound,$NewConf,$action);

    $GroupFound = 0;
    $NewConf = "$ConfFile.new";
    $NewConf = "-" if $DebugLevel > 0;	# Use stdout instead
    
    &GetOptions("v"	=> \$opVerbose,
    		"g=s"	=> \$opGroup,
		"l=s"	=> \$opLog,
		"s=s"	=> \$opSize,
		"t"	=> \$opTruncate,
		"a=i"	=> \$opArchive,
    		"e=s"	=> \$opExecute);

    if (!(defined($opGroup) && defined($opLog) && defined($opSize) &&
    	  (defined($opArchive) || defined($opTruncate) || 
	  defined($opExecute)))) {
	&Usage("gadd -g GROUP -l LOG -s SIZE " .
	       "{ -a ARCHIVENR | -t | -e PROGRAM }\n");		      
    }

    $action = 'A' if defined($opArchive);
    $action = 'T' if defined($opTruncate);
    $action = 'E' if defined($opExecute);

    open(CONF,$ConfFile) || die "cannot open configuration\n";
    open(NEW,">$NewConf") || die "cannot create new conf.\n";

    while(<CONF>) {
	if (/^#:group\s+/i  || /^-group\s+/i) {
	    $Group = $';
	    chop($Group);
	    $Group =~ s/\s*//g;
	    if ($Group eq $opGroup) {
		$GroupFound = 1;
	    }		
	}
	elsif (/^\s*$/) {
	    if ($GroupFound == 1) {
		$GroupFound = 2;	# Don't add it again
	    	printf NEW "%s\n", &makeRecord;
		next;
	    }	
	} 
	print NEW $_;
    }

    if ($opGroup eq 'common') {
	$GroupFound = 2;
	print NEW &makeRecord;
    }
    close(CONF);
    close(NEW);
    &VerboseAbort("Did not find group $opGroup!\n") if ($GroupFound == 0);

    # Now update the Age resource file, since we are appending
    # the caller will have to SYNC afterwards because we don't know
    # about the current state of the Age file
    
    open(RC,">> $ResrcFile");
    &MakeNewAgeEntry(\*RC, $opLog, substr($action,0,index($action,' ')));
    close(RC);
    &FixPermissions($ResrcFile);

    &BackupFileAndRefresh($ConfFile,$NewConf);
}

#-------------------------------
# DeleteLog() :: del
#	Deletes a log from the configuration. 
#	Requires a SYNC afterwards as the Age file
#	is not updated here. The tagged action must be specified.
#
sub DeleteLog {
    my($NewConf,$log,$rest,$thresh,$tag);

    $NewConf = "$ConfFile.new";
    $NewConf = "-" if $DebugLevel > 0;	# Use stdout instead
    &GetOptions("v"	=> \$opVerbose,
		"l=s"	=> \$opLog,
		"t=s"	=> \$opType);

    if (!defined($opLog) || ($opType ne 'A' && $opType ne 'T' && $opType ne 'E')){
	&Usage('del -l LOG -t {A|T|E}');
    }
    open(CONF,$ConfFile) || die "cannot open configuration\n";
    open(NEW,">$NewConf");

    while (<CONF>) {
    	($log,$thresh,$tag,$rest) = split(/\s+/,$_,4);
	$tag = substr($tag,0,1);
	$tag =~ tr/a-z/A-Z/;
	print NEW unless ($log eq $opLog  && $tag eq $opType);
    }
    close(CONF);
    close(NEW);

     &BackupFileAndRefresh($ConfFile,$NewConf);
}

#-------------------------------
# WhenDidWe() :: when
#	When did we last processed that log, we can apply 
#	any of the ATE keys, if none then the first found
#	is reported.

sub WhenDidWe {
    my($didWhat, $toWhichLog) = @_;
    my($tDay,$tMon,$tYear,$Today);

    &Usage('when LOG { A | T | E }') if ($toWhichLog eq "");

    ($tDay,$tMon,$tYear) = unpack('C A3 I',&GetCurrentDate);
    $Today = "$tDay $tMon $tYear";

    &ReadTimeLog;
    printf "%s last %s'ed on %s  ", $toWhichLog, $didWhat,
				    &TimeLogStamp($toWhichLog,$didWhat); 
    printf "Aged %d days\n", &DayCount($Today) - 
			     &DayCount(&TimeLogStamp($toWhichLog,$didWhat));
}

#-------------------------------
# InitializeRepositories() :: initrepos
#	Create the repositories/directories needed for archival
#	if they do not exist taking into account the current options.
#	  Use after init and whenever a new entry (single/group) is
#	made or when an entry is changed into `archive'.

sub InitializeRepositories {
    my($name, $sz, $act, $rest, $exit_val);

    $exit_val = 0;
    open(CONF,$ConfFile) || die "cannot open configuration\n";

    while (<CONF>) {
        chop;
	if (/^#:options\s+/i  || /^-options\s+/i) {
	    &ReadOptions($', \%cfg);
	    next;
	}
	if (/^#:global\s+/i   || /^-global\s+/i) {
	    $cfg{'global'} = $';
	    chomp($cfg{'global'});
	    next;
	}
	if (/^#:group\s+/i    || /^-group\s+/i) {
	    $cGroup{'name'} = $'; 	# By now local/global must be known
	    chomp $cGroup{'name'};
	    next;
	}
        if (/^#\s*ENDGROUP/i || /^$/ || /^\s*$/) {
	    $cGroup{'name'} = '';
	    next;
	}
        next if (/^#/);		
	next if (!/^\//);	# Only logs, obviously are with absolute path

        ($name, $sz, $act, $rest) = split(" ",$_,4);
	$rest = &ValidateRepository(\$Repository,
	                            \%cfg, \%cGroup, 
				    &dirname($name));
	print "$.: ($cGroup{'name'}) " if ($rest > 0);
	if ($rest == 1) { print "local/global option missing! forgiving.\n"; }
	if ($rest == 2) { print "$cfg{'global'} is not absolute. Fatal.\n"; }
	if ($rest == 3) { 
	    print "$Repository does not exist. Creating...";
	    # Make sure the root of the repository exists or is created!
	    if ( !( -e $cfg{'global'} && -d $cfg{'global'} ) ) {
		$rest = mkdir($cfg{'global'},0770);
		if ($rest == 0) { 
		    print "root failed,"; 
		}
	    }
	    $exit_val += $rest;
	    # Now proceed with the group's repository
	    $rest = mkdir($Repository,0770);
	    if ($rest == 1) { print "done.\n"; }
	    else { print "failed with $!\n."; }
	}
    }

    close(CONF);
    exit $exit_val;
}

#-------------------------------
# NewConfiguration() :: newconf

sub NewConfiguration {
    my ($opGlobal, $opLocal, $opForce, $hostname);

    &GetOptions("v"		=> \$opVerbose,
		"global=s"	=> \$opGlobal,
		"local"		=> \$opLocal,
		"force"		=> \$opForce);

    if (($opGlobal eq '' && !defined($opLocal)) ||
        (defined($opGlobal) && defined($opLocal))){
	&Usage('newconf {-global GLOBAL_REPOSITORY | -local}');
    }

    if ( -e $ConfFile ) {
    	if ( !defined($opForce) ) {
	    &VerboseAbort("Newconf - $ConfFile exists, use -force to override\n");
	}
    }

    $hostname = `hostname 2> /dev/null`;
    chomp($hostname);

    open(CONF,"> $ConfFile") || die "cannot open configuration\n";
    print CONF '#' x 72, "\n",
               "# Chklogs v1.9 configuration file for host $hostname\n#\n",
	       "# Chklogs copyright (c)1995,1996,1997 D. Emilio Grimaldo T.\n",
	       "#\n\-Options  ";
    if (defined($opLocal)) {
        print CONF "local\n-Global   /var/log/chklogs";
    } else {
        print CONF "global\n-Global   $opGlobal";
    }
    print CONF "\n\n";
    close(CONF);
}

sub Version {
    if ($opVersion) {
    	printf "chklogsadm v%s ,Module v%s\n", $cvsId, &GetLibVersion;
	exit 0;
    }
}

#=====================================================
# MAIN
#=====================================================

&BeginChklogs;
&initialize;
&GetConfigVars();

if ($#ARGV == -1 || substr($ARGV[0],0,1) eq "-") {
    printf "** c h k l o g a d m  %s/%s **",$cvsId,&GetLibVersion();
    &Usage("{ init | newconf | sync | gadd | when | newgroup | rmgroup | del | initrepos } <Options>");
}

# Service is one of:
#	newconf		Creates a new configuration file (the heading)
#	gadd		Add a new log to an existing group
#	newgroup	Adds a new group to configuration	
#	init		Initializes Age resource and config.
#	rmgroup		Removes a group header from the configuration
#	del		Delete a log from the configuration	
#	sync		Synchronize config & age resources
#	when		Report when a log was last processed	
#	initrepos	Creates the archive repositories 
$Service = $ARGV[0];
shift;

&NewConfiguration if $Service eq 'newconf';
&InitAgeLog() if $Service eq "init";
&InitializeRepositories() if $Service eq "initrepos";
&AddGroup()   if $Service eq "newgroup";
&RmGroup()    if $Service eq "rmgroup";
&AddToGroup() if $Service eq "gadd";
&DeleteLog()  if $Service eq "del";
&SyncAgeLog() if $Service eq "sync";
&IdentifySyslog(0) if $Service eq "syslog";		# console
&WhenDidWe($ARGV[1],$ARGV[0])  if $Service eq "when";	# console
exit 0;
