#!/usr/bin/perl -w
#
# incoming.pl - Handle an incoming message for syssumm
# 
# Synopsis:
# 
#	incoming.pl [ -d ]
# 
# The "-d" command line option turns out debug logging.  All
# output is written to a file called $DebugLogFile which is
# located in the $SyssummUserid home directory.  All output is
# appended.  These is not alot of output for each message.
# 
# This script assumes that we are invoked for each message
# from sendmail (ie., that we only get one message at a time).
# 
# This script is usually run from a .forward file.
# 
#	"|/syssumm/home/directory/incoming.pl"
# 
# If you are debugging incoming.pl and are using the "-d" command
# line option, it must be *inside* the double quotes:
# 
#	"|/syssumm/home/directory/incoming.pl -d"
# 
# If you are running smrsh (in sendmail), then the incoming.pl
# script (and probably perl) must be in the smrsh control directory.
# Sometimes this directory is named something like /usr/adms/sm.bin
# while other times it is as simple as /etc/smrsh.
# 
# Note that the exit status that this script returns to sendmail
# is critical because from it, sendmail determines whether 
# to unqueue the message (delivered) or return it to sender...
# 
# Some of the default values will suggest that this script was
# developed on a LINUX box (RH 5.1).
# 
# ==============================================================
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 
# 2 of the License, or (at your option) any later version.
# 
# 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.
# 
# Original source:
# 	Copyright (c) 1999, 2000 SAIC (Bruce W. Mohler)
# 
# Contributed source:
# 	Copyright (c) 2000 Gustavo Chaves
# 	Copyright (c) 1999, 2000 Frank Crawford
# 	Copyright (c) 2000 Gary Weimer
# 
# ==============================================================
# 
# $Header: /export/home/mohlerb/syssumm/web.server/RCS/incoming.pl,v 1.17 2000/05/21 23:44:09 mohlerb Exp mohlerb $
# 
# $Revision: 1.17 $
# 
# $Log: incoming.pl,v $
# Revision 1.17  2000/05/21 23:44:09  mohlerb
# changes from Gustavo Chaves including:
#  I don't like to receive notifications when nothing changes but the
# General:LastUpdate field in each machine profile.  I changed
# NotifyAdministrator to check for this and be silent in this case.
#
# Revision 1.16  2000/05/04 21:57:46  mohlerb
# changes from Bruce Mohler including:
# code added for debugging incoming profile merge process; I
# suspect that it has problems but I can't see enough debug
# data to confirm or deny
#
# Revision 1.15  2000/04/04 02:29:52  mohlerb
# changes from Frank Crawford including:
# automatically generate the appropriate 'use lib' statement
#
# Revision 1.14  2000/03/16 23:01:10  mohlerb
# changes from Bruce Mohler including:
# changes to support re-write LoadConfigurationFile function;
# note that the CreateConfigurationFile function no longer
# exists; a single argument to LoadConfigurationFile function
# (boolean) which states whether to try to fix problems if
# one or more configuration tags are missing; this is only
# true in install.pl, false for all other scripts; therefore
# if tags are added to the configuration file in later versions,
# it will automatically prompt for new values and add them to
# the file
#
# Revision 1.13  2000/03/02 22:08:45  mohlerb
# changes from Gary Weimer including:
# modified NotifyAdministrator to skip the mail command if
# there is no one to mail to
#
# Revision 1.12  2000/02/23 19:27:01  mohlerb
# changes from Bruce Mohler including:
# s/profile/summary/g for consistency on all generated HTML
# and perl source; added comment in EnterSystemName to remind
# people that they can enter a pattern, not just a nodename;
# added border to GenerateShowall table output
#
# Revision 1.11  2000/02/04 20:54:49  mohlerb
# changes from Bruce Mohler including:
# added variable $HostNameType to common.pm; it has 2 possible
# values: "FQDN" or "hostname"; "FQDN" is the default value which
# emulates the original behavior of creating the subdirectories
# under <docroot>/syssumm with the fully qualified domain name;
# "hostname" merely uses the hostname for the subdirectory name
# (and query searches)
#
# Revision 1.10  1999/08/09 22:18:32  mohlerb
# changes from Bruce Mohler including:
# more debug output including copying current message to "last.message"
# file in syssumm home directory along with logging to "incoming.log"
#
# Revision 1.9  1999/08/06 21:54:49  mohlerb
# changes from Paul Farrall including:
# changed default path to Perl to /usr/bin
#
# Revision 1.8  1999/07/02 16:06:51  mohlerb
# changes from Bruce Mohler including:
# snapshot of major revisions to web server scripts
# factored many common functions into common.pm (different from
# common.pm in remote sub-directory!!!)
# added support for creating and maintain list files for sysquery.pl
# lists (OS, Vendor, Location, Organization) in an expandable fashion
# all scripts were tested except for sysquery.pl
#
# Revision 1.7  1999/06/18 21:19:54  mohlerb
# changes from Frank Crawford including:
# Mail back differences in summary as body of mail message.
#
# Revision 1.6  1999/06/18 17:10:36  mohlerb
# changes from Bruce Mohler including:
# changes to support additional CUSTOMIZE field based on Paul's
# prior patch - these changes are not tested yet!!!
#
# Revision 1.5  1999/06/07 21:24:49  mohlerb
# changes from Bruce Mohler including:
# reverted MailCommand *back* to mailx not mail with
# leading comments on why this needs to be this way
#
# Revision 1.4  1999/06/05 00:25:00  mohlerb
# changes from Bruce Mohler including:
# the CUSTOMIZE fields are inserted during the installation
# process by using the install.pl script
#
# Revision 1.3  1999/05/27 14:11:20  mohlerb
# changes from Keong Lim including:
# added Header RCS tag above Revision tag
#
# Revision 1.2  1999/05/24 23:48:33  mohlerb
# changes from Frank Crawford including:
# Limit the return from split so that values can include the
# split character e.g. a mac address with `:'.
#
# Revision 1.1  1999/05/14 22:54:12  mohlerb
# Initial revision
#

use Getopt::Std;
use FileHandle;
use Sys::Hostname;
BEGIN {
	unless (($DirName = $0) =~ s:/[^/]*$::) {
		$DirName = '.';
	}
}
use lib $DirName;
use common;

#######################################################################
# Global Variables
#######################################################################

# set by command line args
$Debug = 0;

$DebugLogFile = "incoming.log";			# in $SyssummUserid home dir
$LastMessageFile = "last.message";		# in $SyssummUserid home dir

# 
# At run-time, certain global variables are defined from a
# persistent configuration file by the function LoadConfigurationFile
# in common.pm.  Refer to the source in that Perl module to 
# determine which variables are defined.
# 
# In addition, certain variables are exported into the calling
# scripts main:: name space including the syssumm home directory,
# uid, and gid.
# 

@Headers = ( );
@Body = ( );
$RemoteSystemName = "";

$PathToSummaryDirectory = "";
$PathToSummary = "";

# where commands are located
$DiffCommand = "/usr/bin/diff";


#######################################################################
# Subroutines
#######################################################################

sub ProcessCommandLine
{
	if ( getopts('d') ) {
		if ( $opt_d ) { $Debug = 1; }
	}
	else {
		die "There were problems processing the command line.";
	}

	LoadConfigurationFile(0);

	# XXX - DEBUGGING
	my $Firecracker = "=" x 75;
	print STDERR "\n$Firecracker\n$Firecracker\n";
	$Hostname = hostname();
	$Timestamp = localtime();
	print STDERR "\nincoming.pl on $Hostname at $Timestamp\n";

	if ( $Debug ) {
		my $PathForLogFile = "$SyssummHomeDirectory/$DebugLogFile";

		# open up the debug log file
		open DEBUGLOG, ">>$PathForLogFile" or
			die "Couldn't open $PathForLogFile for output (append): $!";

		DEBUGLOG->autoflush(1);

		# create an initial timestamp
		print DEBUGLOG "\n$Firecracker\n$Firecracker\n";
		print DEBUGLOG "incoming message on $Hostname at $Timestamp\n";
	}

	return;
}


#
# Let's read the entire message into an array
# 

sub SlurpInMessage
{
	my @Message = <STDIN>;
	chomp(@Message);
	# burp...

	LeaveInLastMessage(@Message) if $Debug;

	SplitOutHeadersAndBody(@Message);
	DumpHeadersAndBody() if $Debug;
	IdentifyRemoteSystem();

	return;
}


sub LeaveInLastMessage (@)
{	my @Message = @_;

	my $PathToLastMessage = "$SyssummHomeDirectory/$LastMessageFile";
	open LASTMESSAGE, ">$PathToLastMessage" or
		die "Couldn't open $PathToLastMessage for output: $!";
	foreach $Line (@Message) {
		print LASTMESSAGE "$Line\n";
	}
	close LASTMESSAGE;

	return;
}


sub SplitOutHeadersAndBody (@)
{	my @Message = @_;

	my $Header = 1;
	foreach $Line (@Message) {
		if ( $Header ) {
			# look for the empty line separating
			# the headers from the message body
			if ( ! $Line ) {
				$Header = 0;
				next;
			}
			push @Headers, $Line;
		}
		else {	# Body
			push @Body, $Line;
		}
	}

	return;
}


sub DumpHeadersAndBody ()
{
	my $NbrOfLines = scalar(@Headers);
	print DEBUGLOG "\n$NbrOfLines lines read in headers\n";
	foreach $Line (@Headers) {
		print DEBUGLOG "\t$Line\n";
	}
	$NbrOfLines = scalar(@Body);
	print DEBUGLOG "\n$NbrOfLines lines read in message body\n";
	foreach $Line (@Body) {
		print DEBUGLOG "\t$Line\n";
	}

	return;
}


sub IdentifyRemoteSystem ($)
{	my $SubjectLine = "";

	foreach $Line (@Headers) {
		( $SubjectLine = $Line ) if $Line =~ /^Subject:/;
	}
	print DEBUGLOG "Subject Line is $SubjectLine\n" if $Debug;

	# sanity check
	if ( $SubjectLine eq "" ) {
		die "Subject line not detected...: $!";
	}

	# 
	# This should be a fully qualified domain name 
	# (e.g., ornithomimus.eg.example.com)
	# 
	my @Fields = split(/ /, $SubjectLine);
	$RemoteSystemName = $Fields[$#Fields];

	# 
	# How do we want our sub-directories named?
	# 
	# This is controlled by the $HostNameType variable
	# defined in common.pm.  This variable should have
	# only one of two possible values: "FQDN" or "hostname".
	# 
	if ( $HostNameType eq "hostname" ) {
		$RemoteSystemName = (split('\.', $RemoteSystemName))[0];
	}
	# otherwise, leave the name alone as it is already FQDN

	print DEBUGLOG "Remote system name is $RemoteSystemName\n" if $Debug;

	return;
}


# 
# Each profile lives in it's own directory under the
# web server directory tree.  If the directory doesn't
# already exist, then just create the directory, plop
# the information in the correct file, and exit...
# 

sub CreateDirectoryIfNecessary
{
	# does the $SyssummDirectory exist?
	my $PathToSyssummDirectory = "$DocrootDirectory/$SyssummDirectory";
	if ( ! -d "$PathToSyssummDirectory" ) {
		die "Couldn't find the syssumm directory: $PathToSyssummDirectory: $!";
	}

	# what is the path to the directory for this remote system?
	$PathToSummaryDirectory = "$PathToSyssummDirectory/$RemoteSystemName";
	print DEBUGLOG "Directory path is $PathToSummaryDirectory\n" if $Debug;

	# does the remote system directory exist?
	if ( ! -d "$PathToSummaryDirectory" ) {
		# nope, let's create it
		mkdir $PathToSummaryDirectory, 0755 or
			die "Couldn't create directory $PathToSummaryDirectory: $!";
		print DEBUGLOG "New directory created\n" if $Debug;

		# make sure ownership and permissions are correct
		chown($SyssummUid, $SyssummGid, $PathToSummaryDirectory) or
			die "Couldn't change permissions for $PathToSummaryDirectory: $!";
	}

	return;
}


# 
# If the file doesn't already exist in the remote
# system directory, just dump it there.  If it does
# exist, read it in and overwrite old (non-protected)
# values with those that just arrived in e-mail...
# 
# Note that in all cases, the phrase "PROTECTED" will
# be replaced with "To be provided.".
# 

sub CopyOrMerge
{
	# what is the actual path to the profile?
	$PathToSummary = "$PathToSummaryDirectory/$SummaryFileName";
	print DEBUGLOG "Summary path is $PathToSummary\n" if $Debug;

	if ( -f "$PathToSummary" ) {
		print DEBUGLOG "Merging values into existing profile\n" if $Debug;
		MergeValuesIntoExistingFile();
	}
	else {
		print DEBUGLOG "Creating a new profile\n" if $Debug;
		CreateNewFile();
	}

	return;
}


sub CreateNewFile
{
	open OUTPUT, ">$PathToSummary" or
		die "Couldn't open $PathToSummary for output: $!";
	foreach $Line (@Body) {
		$Line =~ s/PROTECTED/To be provided./;
		print OUTPUT "$Line\n";
	}
	close(OUTPUT);
	print DEBUGLOG "New profile created\n" if $Debug;
	NotifyAdministrator("New system profile created for $RemoteSystemName");

	# do this now so that we can check for updates to the list files...
	LoadSummary($RemoteSystemName);

	return;
}


sub MergeValuesIntoExistingFile
{
	# XXX DEBUGGING
	print DEBUGLOG "\nExisting system summary values\n" if $Debug;
	open SUMMARY, "<$PathToSummary" or
		die "Couldn't open $PathToSummary for input: $!";
	my @SummaryDump = <SUMMARY>;
	chomp @SummaryDump;
	close SUMMARY;
	foreach $Line (@SummaryDump) {
		print DEBUGLOG "$Line\n";
	}

	LoadSummary($RemoteSystemName);
	RenameExistingFile();
	MergeAndOverwriteFile();
	print DEBUGLOG "\nValues merged into existing profile\n" if $Debug;
	NotifyAdministrator("System profile for $RemoteSystemName updated");

	# XXX DEBUGGING
	print DEBUGLOG "\nFinal system summary values\n" if $Debug;
	open SUMMARY, "<$PathToSummary" or
		die "Couldn't open $PathToSummary for input: $!";
	my @SummaryDump = <SUMMARY>;
	chomp @SummaryDump;
	close SUMMARY;
	foreach $Line (@SummaryDump) {
		print DEBUGLOG "$Line\n";
	}

	return;
}


sub RenameExistingFile
{
	# what will the new name be?
	my $PathToSummaryBackup = "$PathToSummary$PreviousFileSuffix";
	rename $PathToSummary, $PathToSummaryBackup or
		die "Couldn't rename $PathToSummary: $!";

	print DEBUGLOG "Existing profile renamed to $PathToSummaryBackup\n" if $Debug;

	return;
}


# 
# Read each line of the incoming file.  If it's a PROTECTED
# field and there's a value in the applicable hash for that
# field, retain the prior value, otherwise write the field
# to the new profile.
# 
# XXX This merging process is driven by the incoming file.  If
# there was a problem with the remote syssumm process and 
# fields were missing, then fields that had been loaded from
# the prior profile would be dropped as well...
# 

sub MergeAndOverwriteFile
{
	open OUTPUT, ">$PathToSummary" or
		die "Couldn't open $PathToSummary for output: $!";
	print DEBUGLOG "\nBeginning to merge system summary\n" if $Debug;
	foreach $Line (@Body) {
		print DEBUGLOG "$Line\n" if $Debug;
		if ( $Line =~ /PROTECTED/ ) {
			print DEBUGLOG "\tA PROTECTED field " if $Debug;
			# is there a prior value?
			my ( $Hash, $Key, $Value ) = split(/:/, $Line, 3);
			if ( exists ${$Hash}{$Key} ) {
				$Line = "$Hash:$Key:${$Hash}{$Key}";
				$Line =~ s/To be provided./PROTECTED/;
				print DEBUGLOG "restoring prior value ($Value)\n" if $Debug;
			}
			else {
				print DEBUGLOG "storing\n" if $Debug;
				# $Line =~ s/PROTECTED/To be provided./;
			}
		}
		print OUTPUT "$Line\n";
	}
	close(OUTPUT);

	return;
}


# 
# XXX - does this function actually work?
# 

sub NotifyAdministrator
{	my $Message = shift;
	my $Command = "";

	# if the admin's email address is blank, don't bother
	# notifying them of new incoming profiles
	return if ($AdminEmailAddress eq "");

	my $PathToSummaryBackup = "$PathToSummary$PreviousFileSuffix";

	# we're assuming that $MailCommand is in our path...
	if (-f $PathToSummaryBackup) {
		# If the only difference is the LastUpdate field don't bug
		# the admin with this.
		my $Diff = `$DiffCommand $PathToSummaryBackup $PathToSummary`;
		my $nlines = $Diff =~ tr/\n/\n/;
		return if $nlines == 4 && $Diff =~ /General:LastUpdate/;
		open(MAIL, "|$MailCommand -s \"$Message\" $AdminEmailAddress")
			or die "Can't run $MailCommand: $!";
		print MAIL $Diff;
		close(MAIL);
	}
	else {    
		system("$MailCommand -s \"$Message\" $AdminEmailAddress <$PathToSummary 2>/dev/null")
			and die "Can't run $MailCommand: $!";
	}       

	return;
}


sub Cleanup
{
	if ( $Debug ) {
		# close the Debug Log file
		close DEBUGLOG;
	}

	return;
}


#######################################################################
# Mainline
#######################################################################

ProcessCommandLine();
SlurpInMessage();
CreateDirectoryIfNecessary();
CopyOrMerge();
UpdateLists();
Cleanup();

#######################################################################
# We're done!
#######################################################################
exit(0);
