#!/usr/bin/perl -w
#
# sysquery.pl - allow user to specify what system summary they wish to see
# 
# Synopsis:
# 
#	sysquery.pl			returns a form for searching
#	sysquery.pl?target=form		returns a form for searching
#	sysquery.pl?target=showall	returns all systems with summaries
#	sysquery.pl?target=<name>	returns summary for <name>
#	sysquery.pl?target=showall&os=""&location=""&vendor=""&organization=""
#					returns all systems which meet
#					search criteria
#	sysquery.pl?target=frames	automatically handles frames as
#					described below. Returns frames with
#					showall in left frame & form in right
#					frame
# 
# This Perl script is capable of generating frame-based html.  It 
# is possible to specify a frame in which to display the target's 
# summary information. You do this by specifying the name of the 
# frame as a cgi-argument :
#
#	sysquery.pl?target=form&frame=<frame_name>
#	sysquery.pl?target=showall&frame=<frame_name>
#	sysquery.pl?target=<fqdn>&frame=<frame_name>
# 
# See sub GenerateFrames for example.
# 
# This Perl script was designed to both create a form prompting a
# user for the name of a system to display a summary for as well
# as process the output of that form.
# 
# The web pages generated by this script are really simple.  Therefore,
# no other Perl modules were employed in their creation.  This script
# exists just to give you an idea of how to implement the "back end"
# of syssumm.
# 
# This Perl script is designed to be called as a CGI program by a
# web server.
# 
# ==============================================================
# 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 Martin Andrews
#	Copyright (c) 1999, 2000 Frank Crawford
#	Copyright (c) 1999, 2000 Paul Farrall
#	Copyright (c) 2000 Ken Monville
#	Copyright (c) 1999, 2000 Bart Swennen
# 
# ==============================================================
# 
# $Header: /export/home/mohlerb/syssumm/web.server/RCS/sysquery.pl,v 1.37 2000/06/08 04:15:11 mohlerb Exp mohlerb $
# 
# $Revision: 1.37 $
# 
# $Log: sysquery.pl,v $
# Revision 1.37  2000/06/08 04:15:11  mohlerb
# changes from Martin Andrews including:
# fixed a vicious typo
#
# Revision 1.36  2000/05/08 15:31:25  mohlerb
# changes from Gary Weimer including:
# added functionality for target=frames feature
#
# Revision 1.35  2000/04/27 18:02:20  mohlerb
# changes from Bruce Mohler including:
# when a summary is not found and an error web page is generated,
# use the same GenerateHeader and GenerateFooter functions so
# that things look consistent
#
# Revision 1.34  2000/04/27 17:38:31  mohlerb
# changes from Ken Monville including:
# These patches enable the System Handle to become a link
# if $SystemHandleUrl is set; this is similar to the System
# Handle being a link in sysquery.pl?target=showall
#
# Revision 1.33  2000/04/22 23:18:07  mohlerb
# changes from Ken Monville including:
# includes an addtional field to the sysquery.pl showall page;
# now displays the System Handle in the table; includes support
# to make a link from the System Handle; If $ServerHandleUrl
# in common.pm is left empty, the system handles are just
# printed without being a link, however, if $ServerHandleUrl
# contains a value, the Server Handle is appended to it and made
# into an <a href=""> tag
#
# Revision 1.32  2000/04/22 22:42:50  mohlerb
# changes from Ken Monville including:
# added anchor <a> tags to each category of the summary page;
# it's useful in our environment
#
# Revision 1.31  2000/04/04 03:30:13  mohlerb
# changes from Frank Crawford including:
# Patch to add both the current path and syssumm's home
# directory to @INC
#
# Revision 1.30  2000/04/04 02:29:52  mohlerb
# changes from Frank Crawford including:
# automatically generate the appropriate 'use lib' statement
#
# Revision 1.29  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.28  2000/03/16 18:25:04  mohlerb
# changes from Gary Weimer including:
# fixed frames display (was displaying summary in current frame
# instead of summary frame)
#
# Revision 1.27  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.26  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.25  2000/02/01 23:32:51  mohlerb
# changes from Bruce Mohler including:
# fixed inclusion of external text files to require prefix of
# "#include"; can have multiple external text files included;
# can have graphical files included in the list and they will
# be wrapped in <img src=...>; recognizes jpg, jpeg, gif, png
#
# Revision 1.24  1999/10/25 22:05:07  mohlerb
# changes from Bruce Mohler including:
# fixed format of message output when system cannot be found
#
# Revision 1.23  1999/10/23 23:24:50  mohlerb
# changes from Bruce Mohler including:
# made all references to "/cgi-bin/$CGIScriptName" consistent since
# some were prepended with $WebServerUrl and some weren't - they
# all are now; Paul English found this bug
#
# Revision 1.22  1999/10/20 20:05:32  mohlerb
# changes from Bruce Mohler including:
# support for $DOCROOT/syssumm/syssumm.header file for customizing
# web pages at your local site
#
# Revision 1.21  1999/10/13 19:17:37  mohlerb
# changes from Bruce Mohler including:
# handle uninitialized values that may exist in the syssumm.format
# file but are not yet in the actual profile for a particular system
#
# Revision 1.20  1999/09/27 22:01:33  mohlerb
# changes from Bruce Mohler including:
# set width of table on profile output so that tables line up nicely
#
# Revision 1.19  1999/09/02 22:03:53  mohlerb
# changes from Bruce Mohler including:
# in my testing I'll sometimes have a directory under /htdocs/syssumm
# for a system that doesn't yet have a profile in it - this breaks
# sysquery in that it stops building the table for a "showall" or
# "search" and you end up with table data in the HTML code sent to
# the browser, but none visible because there's no </table> - this
# problem is fixed - if there's no profile, then the system is bypassed
# the search button on the search section of the "showall" web page
# was moved from below to the right of the drop down lists
#
# Revision 1.18  1999/08/31 15:12:09  mohlerb
# changes from Paul Farrall including:
# The email address for the webmaster was listed as a http link,
# not a "mailto" and the address itself had a \ inserted before
# the @.
#
# Revision 1.17  1999/08/18 19:25:47  mohlerb
# changes from Bruce Mohler including:
# made error handling from a failed inline inclusion alittle
# more friendly (rather than "die..." call going to httpd
# error_log), it will print a descriptive message in the
# profile)
#
# Revision 1.16  1999/08/06 21:54:49  mohlerb
# changes from Paul Farrall including:
# changed default path to Perl to /usr/bin
# commented out GenerateListOfVendors() and GenerateListOfOSs
# since they aren't needed anymore
# Added some comments to GenerateFormList to make it clearer
# what this function is doing
# Fixed URL link in $Message var in ValidateTarget().  It was
# missing path info
#
# Revision 1.15  1999/07/21 18:56:56  mohlerb
# changes from Bruce Mohler including:
# made ref to $WebMaster in GenerateFooter a little more
# meaningful; cleaned up references to <body>; added initial
# <html> tag
#
# Revision 1.14  1999/07/02 21:59:26  mohlerb
# changes from Bruce Mohler including:
# fixes after testing sysquery.pl
#
# Revision 1.13  1999/07/02 16:27:51  mohlerb
# changes from Frank Crawford including:
# Added option to use a "glob-style" pattern in system names.
#
# Revision 1.12  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.11  1999/06/18 21:22:07  mohlerb
# changes from Bruce Mohler including:
# small fixes after testing changes *I* made following Paul
# Farrall's great upgrade to this script.
#
# Revision 1.10  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.9  1999/06/18 16:55:48  mohlerb
# changes from Paul Farrall including:
# Script now uses CGI module for handling parameters passed to it.
# Added support for the following CGI names. "os,vendor,organization,location".
# If no parameters are defined, GenerateForm is called automatically.
# Removed GenerateWebPage function (no longer used).
# Removed $AdminEmailAddress.  It wasn't being used for anything.
# Added function GenerateFooter.
# Modified function ValidateTarget to return a more meaningful message
# if the target is not found.
# Modified function GenerateForm to add an option to search for systems
# by OS, Vendor, Organization, andLocation.  Options are cumulative.
# Replaced link to "sysquery.pl?target=showall" with form button.
# Modified function GenerateShowall to output a table that now shows
# OS and Location.
# Added filtering for options passed by form above.
# Checks if $Frame is defined before trying to use it.
# Fixed broken mailto link in ReturnError.
# Added $WebServerUrl under CUSTOMIZE section.
# In addition, there are some other changes added by Bruce Mohler that
# were not tested before this version was checked in but will when the
# next set of patches from Frank Crawford are.
#
# Revision 1.8  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.7  1999/06/03 19:56:48  mohlerb
# revert to Frank Crawford's original split patch in v1.2
#
# Revision 1.6  1999/06/03 19:41:02  mohlerb
# changes from Jeff Putsch including:
# change split to pattern match.
#
# Revision 1.5  1999/05/28 18:07:56  mohlerb
# changes from Bart Swennen including:
# support for frames in sysquery.pl output.
#
# Revision 1.4  1999/05/27 14:37:21  mohlerb
# changes from Frank Crawford including:
# Convert from IO::Handle to FileHandle module (for backward compatibility).
#
# 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:49:19  mohlerb
# changes from Frank Crawford including:
# limit the returned values from split to include the split character,
# and make the URL for sysquery a variable in the HTML output.
#
# Revision 1.1  1999/05/14 22:54:12  mohlerb
# Initial revision
#

use CGI qw(param);
use FileHandle;
use Sys::Hostname;
BEGIN {
	my $DirName = $0;
	unshift(@INC, ($DirName =~ s:/[^/]*$::) || '.');
	unshift(@INC, ((getpwnam("syssumm"))[7]) || '.');
}                                                                               
use common;

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

# static variables
$Debug = 0;					# set this to 1 during debugging

$TempDirectory = "/tmp";
$DebugLogFile = "sysquery.log";
$DEBUGLOG = "";					# uc to simulate file handle

# 
# 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.
# 

$PathToSyssummDirectory = "";

# what do we want this script to do? "form", "showall", or "system name"
$Target = "";				# name of system summary to display
$Frame = "";				# sent to frame <frame>
# search terms on the form
$ServerOS = "";
$Location = "";
$Org = "";
$Vendor = "";
$Wildcard = "";

@FormatInfo = ( );
$ThisLineNumber = 0;
$ThisManyColumns = 0;

$TableWidth = "95%";

@GraphicFileSuffixes = (
	"jpg",
	"jpeg",
	"gif",
	"png",
);


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

########################## Figure out input ########################## 

sub ParseInputFromWebServer
{	my $Query = "";

	LoadConfigurationFile(0);

	# open up log file, append, no cache
	if ( $WebServer eq "netscape" ) {
		# 
		# The Netscape server can't seem to detect the
		# difference between STDOUT and STDERR...
		# 
		my $PathForLogFile = "$TempDirectory/$DebugLogFile";
		open DEBUGLOG, ">>$PathForLogFile" or
			die "Couldn't open $PathForLogFile for output (append): $!";
		DEBUGLOG->autoflush(1);
		$DEBUGLOG = \*DEBUGLOG;
	}
	else {
		$DEBUGLOG = \*STDERR;
	}

	# print out this line whether or not we've enabled $Debug!
	$Hostname = hostname();
	$Timestamp = localtime();
	print $DEBUGLOG '-' x 75;
	print $DEBUGLOG "\nsysquery.pl on $Hostname at $Timestamp\n"; 

	$PathToSyssummDirectory = "$DocrootDirectory/$SyssummDirectory";

	if (param()) {

		# Get cgi parameters
		$Target = param("target");
		$Frame = param("frame");
		$ServerOS = param("os");
		$Location = param("location") ;
		$Org = param("organization");
		$Vendor = param("vendor");

		if ( $Debug ) {
			if ( $Target ) { print $DEBUGLOG "\tTarget is $Target\n"; }
			if ( $Frame ) { print $DEBUGLOG "\tFrame is $Frame\n"; }
			if ( $ServerOS ) { print $DEBUGLOG "\tServerOS is $ServerOS\n"; }
			if ( $Location ) { print $DEBUGLOG "\tLocation is $Location\n"; }
			if ( $Org ) { print $DEBUGLOG "\tOrg is $Org\n"; }
			if ( $Vendor ) { print $DEBUGLOG "\tVendor is $Vendor\n"; }
		}

		# Are they asking for a summary or a specific system?
		if ($Target eq "showall") {
			GenerateShowall();
		}
		elsif ( $Target eq "form" ) {
			GenerateForm();
		}
		elsif ( $Target eq "frames" ) {
			GenerateFrames();
		}
		elsif ( $Target =~ tr/*?/*?/ ) {
			($Wildcard = $Target) =~ s/\./\\./g;
			$Wildcard =~ s/([*?])/.$1/g;
			GenerateShowall();
		}
		else {
			# 
			# 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" ) {
				$Target = (split('\.', $Target))[0];
			}
			ValidateTarget();
			GenerateSummary();
		}

	}
	else {  # No parameters passed, so return Form
		GenerateForm();
	}

	# close the log file
	# note that we may be closing STDERR, see above...
	close $DEBUGLOG;

	return;
}


sub ValidateTarget
{
	# 
	# Assume that the $Target is the name of a system
	# to display the summary of
	# 

	# validate system name
	my $SummaryDirectory = "$PathToSyssummDirectory/$Target";

	# does the directory exist?
	if ( ! -d $SummaryDirectory ) {
		my $Message = "A summary doesn't exist for that system.<br>" .
			"For a list of all existing system summaries, click on this link " .
			"<A HREF=\"$WebServerUrl/cgi-bin/$CGIScriptName?target=showall\">Show All Summaries</A><hr>";

		ReturnError(404, "System Summaries - Not found", $Message);
	}

	# change to that directory
	chdir($SummaryDirectory) or 
		die "Could not cd to $SummaryDirectory: $!";

	return;
}


########################## Generate output  ########################## 

# 
# This is the main sysquery form
# 

sub GenerateForm
{
	GenerateHeader("System Summary - Query");

	print <<END_TEXT;

<h1>System Summaries</h1>
<table border="5">

END_TEXT

	# this is a little more modular...
	EnterSystemName();
	SearchForSystems();
	AllowShowAll();

	print <<END_TEXT;

</table>

END_TEXT

	GenerateFooter();

	return;
}


#
# This will create a frames version of the form
#

sub GenerateFrames
{
	print <<END_TEXT;

<html>
<head>
<title>System Summaries</title>
</head>
<frameset  cols="20%,*">
<frame  src="/cgi-bin/sysquery.pl?target=showall&frame=summary_frame" name="all_frame" >
<frame  src="/cgi-bin/sysquery.pl?target=form&frame=summary_frame" name="summary_frame" >
<noframes>
Sorry your browser does not support <b>frames</b>.
Use <a href="/cs-bin/sysquery.pl?target=showall" > this page </a> instead.
</noframes>
</frameset>
</html>

END_TEXT

	return;
}


# 
# This function will generate one or more rows in a
# pre-defined table
# 

sub EnterSystemName
{
	print <<END_TEXT;

<tr border=0>
	<td>
		<center><strong>Enter the name of the system you wish to view the summary of:</strong></center><p>
		<form method=get action="$WebServerUrl/cgi-bin/$CGIScriptName">
		<input name=target size=60>
		<input type=submit value=\Search\>
		</form>
		<center>Or you can enter a pattern and syssumm will list all systems matching that pattern.</center>
	</td>
</tr>

END_TEXT

	return;
}


# 
# This function will generate one or more rows in a
# pre-defined table
# 

sub SearchForSystems
{
	print <<END_TEXT;

<tr border=0>
	<td>
		<center><strong>Or, choose a Subset of systems to display</strong></center><p>
		<table>
			<tr>
				<td>
					<form method=get action="$WebServerUrl/cgi-bin/$CGIScriptName">
					<input name="target" value="showall" type="hidden">
					<strong>Operating System</strong>
				</td>

				<td>

END_TEXT

	GenerateFormList("os", "OSList");

	print <<END_TEXT;

				</td>

				<td>
					<strong>Location</strong>
				</td>

				<td>

END_TEXT

	GenerateFormList("location", "LocationList");

	print <<END_TEXT;

				</td>

				<td rowspan="2" align="center" valign="center">
					&nbsp;&nbsp;&nbsp;&nbsp;<input type=submit value=\Search\>
				</td>

			</tr>

			<tr>
				<td>
					<strong>Vendor</strong>
				</td>

				<td>

END_TEXT

	GenerateFormList("vendor", "VendorList");

	print <<END_TEXT;

				</td>

				<td>
					<strong>Organization</strong>
				</td>

				<td>

END_TEXT

	GenerateFormList("organization", "OrganizationList");

	print <<END_TEXT;
					</form>

				</td>

			</tr>

		</table>

	</td>
</tr>

END_TEXT

	return;
}


# The following function reads config files in the syssumm
# subdirectory of the html root directory.
# These files contain lists of existing vendors, locations, OS,
# and organizations in use at your site.
# The data is mined from the syssumm.info files for each site
# and is maintained by the script "incoming.pl"
# The files can be created initially by running the "updatelists.pl"
# script in the syssumm home directory
#
# The purpose of this function is to generate a pull down menu
# on the form with the data in these files

sub GenerateFormList
{	my $FormListName = shift;
	my $ListFileName = shift;

	print "<select name=\"$FormListName\">\n";
	# we'll always have an "All"
	print "<option value=\"\">All</option>\n";

	my $PathToListFile = "$PathToSyssummDirectory/$ListFileName";

	my @ListFile = ( );
	if ( -f "$PathToListFile" ) {
		open LISTFILE, "<$PathToListFile" or
			die "Couldn't open file $PathToListFile for input: $!";
		@ListFile = <LISTFILE>;
		chomp(@ListFile);
		close(LISTFILE);	
	}

	foreach $Line (@ListFile) {
		print "<option value=\"$Line\">$Line</option>\n";
	}

	print "</select>\n";

	return;
}


# 
# This function will generate one or more rows in a
# pre-defined table
# 

sub AllowShowAll
{
	print <<END_TEXT;

<tr border=0>
	<td>
		<center>
		<strong>Or, show all systems for which summaries exist</strong>
		<form method=get action="$WebServerUrl/cgi-bin/$CGIScriptName">
		<input name="target" value="showall" type="hidden">
		<input type=submit value="Show All">
		</form>
		</center>
	</td>
</tr>

END_TEXT

	return;
}


sub GenerateShowall
{
	GenerateHeader("System Summary - Show All Systems Which Have Summaries");

	print <<END_TEXT;

<h1>Summaries exist for the following systems:</h1>
<p>

END_TEXT

	# Add link for query form if in frames
	if ( $Frame ) {
		print "<tr><td><a href=\"$WebServerUrl/cgi-bin/$CGIScriptName?target=form\" target=$Frame >Click</a> for query form.</td><p>";
	}

	# cd to the syssumm directory and list the systems as links
	chdir($PathToSyssummDirectory) or 
		die "Could not cd to $PathToSyssummDirectory: $!";

	# get list of "files" and list directories
	opendir SYSSUMMDIR, "." or
		die "Couldn't read $PathToSyssummDirectory: $!";
	my @AllFiles = readdir SYSSUMMDIR;
	closedir(SYSSUMMDIR);

	# emit table headings
	print <<END_TEXT;

<table border="1">
<tr>
<th>Hostname</th>
<th>System Handle</th>
<th>Operating System</th>
<th>Location</th>
</tr>

END_TEXT

	foreach $File (sort @AllFiles) {
		# bypass "." and ".."
		next if $File =~ /^\./;
		next if $File eq "lost+found";		# just in case
		next if $Wildcard and $File !~ /$Wildcard/i;

		if ( -d "$PathToSyssummDirectory/$File" ) {

			# does the summary exist?
			if ( ! -f "$PathToSyssummDirectory/$File/$SummaryFileName" ) {
				next;
			}

			LoadSummary($File);

			# 
			# Each of the next four tests run only
			# if those parameters were passed from
			# the form.  If the parameter exists
			# it's value is checked against those
			# returned from LoadSummary.
			# 

			if ( $ServerOS ) {
				next unless $ServerOS eq $Software{"OsName"};
			}

			if ( $Location ) {
				next unless $Location eq $General{"Location"};
			}

			if ( $Vendor ) {
				next unless $Vendor eq $General{"Vendor"};
			}

			if ( $Org ) {
				next unless $Org eq $General{"Organization"};
			}

			$Found++;

			# Don't try to use $Frame unless it's been defined
			if ( $Frame ) {
				print "<tr><td><a href=\"$WebServerUrl/cgi-bin/$CGIScriptName?target=$File\" target=$Frame >$File</a></td>";
			} else {
				print "<tr><td><a href=\"$WebServerUrl/cgi-bin/$CGIScriptName?target=$File\" >$File</a></td>";
			}

			$General{SystemHandle} =~ s/PROTECTED/$ProtectedMessage/;
			if ($SystemHandleUrl && $General{SystemHandle} ne "PROTECTED") {
				print "<td><a href=\"$SystemHandleUrl" . "$General{SystemHandle}\">$General{SystemHandle}</a></td>\n" if $General{SystemHandle};
			} else {
				print "<td>$General{SystemHandle}</td>\n" if $General{SystemHandle};
			}
			print "<td>$Software{OsName} $Software{OsVersion}</td>\n";
			print "<td>$General{Location}</td>\n";
			print "<tr>\n";
		}
	}

	if ( $Found == 0 ) {

	print <<END_TEXT;

<tr>
<td>
</td>
</tr>

</table>

<h1>No machines found with those parameters</h1>
For a list of all existing system summaries, click on this link
<br>\n<A HREF=\"$WebServerUrl/cgi-bin/$CGIScriptName?target=showall\">Show All Summaries</A><hr>

END_TEXT
	}
	else {
		print "</table>\n";
	}

	GenerateFooter();

	return;
}


sub GenerateSummary
{
	GenerateHeader("System Summary - $Target Summary");

	print <<END_TEXT;

<h1>System Summary for $Target:</h1>
<p>
END_TEXT

	LoadFormat();
	LoadSummary($Target);

	ProcessFormatInfo();

	GenerateFooter();

	return;
}


# 
# This function will output the start of the <head>
# section including the <title>.  It will then look
# for an external file that will complete the <head>
# section and begin the <body> section so that the
# user can customize the appearance of the web pages
# for their site.
# 
# If the external file doesn't exist, a minimal set
# of lines closing the <head> section and opening the
# <body> section will be emitted.
# 
# See the Install documentation on the web site for
# examples of setting up the external file.
# 

sub GenerateHeader ($)
{	my $Title = shift;

	print "Content-type: text/html\n\n";

	print "<html>\n";
	print "<head>\n";
	print "<title>$Title</title>\n";

	my $PathToHeaderFile = "$PathToSyssummDirectory/$HeaderFileName";

	if ( -f $PathToHeaderFile ) {
		open HEADER, "<$PathToHeaderFile" or
			die "Couldn't open header file $PathToHeaderFile: $!";
		my @HeaderInfo = <HEADER>;
		close(HEADER);
		print @HeaderInfo;
	}
	else {
		# no external file, emit miminal lines to open <body>
		print "<!-- Built-in header information -->\n\n";
		print "</head>\n\n";
		print "<body>\n";
	}

	return;
}


#
# This does the same as sub GenerateHeader(), except it's for the footer.
# If the footer file is not present, it just closes the body of the html, then+ # displays a simple footer
#

sub GenerateFooter ()
{
	my $PathToFooterFile = "$PathToSyssummDirectory/$FooterFileName";

	if ( -f $PathToFooterFile ) {
		open FOOTER, "<$PathToFooterFile" or
			die "Couldn't open footer file $PathToFooterFile: $!";
		my @FooterInfo = <FOOTER>;
		close(FOOTER);
		print @FooterInfo;
	}
	else {
		# no external file, emit miminal lines to close </html>
		print "<!-- Built-in footer information -->\n\n";
		print "</body>\n";
		print "<br>\n";			# hard-code vertical spacing
		print "<p align=center>\n";
		print "Send comments about this web page to <a href=\"mailto:$WebmasterEmailAddress\">$Webmaster</a>\n";
		print "</p>\n";
		print "</html>\n";
	}

	return;
}


sub LoadFormat
{
	my $PathToSyssummDirectory = "$DocrootDirectory/$SyssummDirectory";
	my $PathToFormatFile = "$PathToSyssummDirectory/$FormatFileName";

	open FORMAT, "<$PathToFormatFile" or
		die "Couldn't open format file $PathToFormatFile: $!";
	@FormatInfo = <FORMAT>;
	chomp(@FormatInfo);
	close(FORMAT);

	return;
}


sub ProcessFormatInfo
{
	# 
	# The information in the format file appears like the
	# following example:
	# 
	#	table columns=2 title=General
	#	  General:NodeName General:Organization
	#	  General:Vendor General:Model
	#	  General:Location-*
	#	  General:DateSystemInstalled
	#	endtable
	# 
	# Note that lines that contain more than one "field"...
	# 
	# A field ending in "-*" is a Multiple Value field.
	# 
	# The format file (example above) drives the order in
	# which fields appear in the HTML-ized output.  The
	# values for each field are derived from the summary.
	# 
	# Note that the "value" of a field may be "#include 
	# <file> ...".  In this case, each file on the line,
	# in the order they appear, are sent out to the browser.
	# The data in each file may contain HTML tags.  If the
	# suffix on the data file appears to be a graphics file
	# (.jpg, .jpeg, or .gif), then the file name is wrapped
	# in an <img src...>filename</img> tag.
	# 
	foreach $Line (@FormatInfo) {
		$ThisLineNumber++;

		# general cleanup
		$Line =~ s/^\s*//;		# dike leading whitespace
		next if $Line =~ /^#/;		# bypass comments
		next if ( $Line eq "" );	# bypass blank lines

		# identify and process commands
		my @Field = split(/ /, $Line);
		if ( $Field[0] eq "table" ) {
			shift @Field;
			StartTable(@Field);
			next;
		}
		elsif ( $Field[0] eq "endtable" ) {
			EndTable();
			next;
		}

		# what's left over is data
		print "<tr>\n";

		# make sure we don't exceed number of columns
		my $NbrOfColumns = scalar(@Field);
		if ( $NbrOfColumns gt $ThisManyColumns ) {
			die "Data at line $ThisLineNumber exceeds max columns";
		}

		# are we going to do any column spanning?
		my $ColumnSpanning = ( $NbrOfColumns < $ThisManyColumns );

		# process each "field"
		my $CurrentColumn = 1;
		foreach $Field (@Field) {

			print $DEBUGLOG "\tProcessing field $Field...\n" if $Debug;

			# handle spanning columns
			my $SpanningArg = "";
			if ( $ColumnSpanning and 
			     $CurrentColumn == $NbrOfColumns ) {
				my $ColumnsToSpan = ( ( $ThisManyColumns - $NbrOfColumns) + 1 );
				$SpanningArg = " colspan=\"$ColumnsToSpan\"";
			}

			# handle the actual field
			my ( $Category, $Subcategory ) = split(/:/, $Field, 2);
			# 
			# if the subcategory ends in -* (as in Disks-*),
			# then expand it and find all of the matching
			# subcategory fields
			# 
			if ( $Subcategory =~ /-\*$/ ) {
				my @Value = ExpandWildcard($Category, $Subcategory);
				$Subcategory =~ s/-\*$//;
				my $Label = TransformSubcategory($Subcategory);
				print "<td$SpanningArg> $Label: <br>@Value</b> </td>\n";
			}
			else {
				my $Value = "Unknown";
				if ( ${$Category}{$Subcategory} ) {
					if ( $Subcategory eq "SystemHandle" &&
						$SystemHandleUrl ) {
						$Value = "<a href=\"$SystemHandleUrl" . "${$Category}{$Subcategory}\">${$Category}{$Subcategory}</a>";
					} else {
						$Value = ${$Category}{$Subcategory};
						$Value =~ s/PROTECTED/$ProtectedMessage/;
					}
				}
				my $Label = TransformSubcategory($Subcategory);

				# XXX - DEBUGGING
				# print $DEBUGLOG "\t\tCategory: $Category, Subcategory: $Subcategory, Value: $Value\n" if $Debug;

				# 
				# If the value starts with "#include"
				# assume that the value is one or more
				# files to be included as the value
				# for this field.
				# 
				if ( $Value =~ /^#include/ ) {
					print "<td$SpanningArg> $Label: ";
					SubstituteFileForValue($Value);
					print "</td>\n";
				}
				else {
					print "<td$SpanningArg> $Label: <b>$Value</b> </td>\n";
				}
			}

			$CurrentColumn++;
		}

		print "</tr>\n";
	}

	return;
}


sub StartTable
{	my @TableArgs = @_;

	# process "command line args"
	foreach $Arg (@TableArgs) {
		my ( $Tag, $Value ) = split(/=/, $Arg, 2);
		if ( $Tag eq "columns" ) {
			$ThisManyColumns = $Value;	# global
		}
		elsif ( $Tag eq "title" ) {
			my $LowerCaseValue = lc($Value);
			print "<h2><a name=\"$LowerCaseValue\">$Value</a></h2>\n";
		}
		else {
			die "Error processing table command arg at line $ThisLineNumber";
		}
	}

	# so, what are you waiting for?
	print "<table border=\"2\" width=\"$TableWidth\">\n";

	return;
}


sub EndTable
{
	print "</table>\n";

	return;
}


# 
# Take a name like "DateSystemInstalled" and
# transform it to "Date System Installed"
# 

sub TransformSubcategory
{	my $Subcategory = $_[0];

	# XXX This was not working properly in the first
	#     version and has been tweaked since.  Please
	#     check for proper working.
#	$Subcategory =~ s/([A-Z][a-z]+)([A-Z])/$1 $2/g;

	# The regular expression above fails  to split words 2/3 in
	# three word names like "DateSystemInstalled".

	# How about this instead.
	# This one splits any pattern like "bA" into "b A"
	# e.g. "Name" => "Name", "NameaNameb" => "Namea Nameb",
	#      "NameaNamebNamec" => "Namea Nameb Namec"
	# note.  Name must end and begin with alphabet character.
	$Subcategory =~ s/([a-z])([A-Z])/$1 $2/g;

	return $Subcategory;
}


sub ExpandWildcard
{	my $Category = $_[0];
	my $Subcategory = $_[1];
	my @Results = ( );

	$Subcategory =~ s/-\*$/-/;

	foreach $Key ( sort keys %{$Category} ) {
		if ( $Key =~ /$Subcategory/ ) {
			push @Results, "<b>${$Category}{$Key}</b><br>";
		}
	}

	return @Results;
}


# 
# Note that this function could really be called
# SubstituteFile(s)ForValue...
# 

sub SubstituteFileForValue
{	my $Value = $_[0];
	my @FilesToInclude = ( );

	print $DEBUGLOG "\tIn SubstituteFileForValue()...\n" if $Debug;

	# clean up the "value"
	$Value =~ s/\s+/ /g;

	# split the value into sub-fields
	my @SubFields = split(/ /, $Value);

	# build list of files to include
	my $ThisShouldBeFileName = 0;
	foreach $SubField (@SubFields) {
		if ( $ThisShouldBeFileName ) {
			push @FilesToInclude, $SubField;
			$ThisShouldBeFileName = 0;
			next;
		}
		if ( $SubField eq "#include" ) {
			$ThisShouldBeFileName = 1;
			next;
		}
	}

	# calculate where the images are stored (just in case)
	my $PathToSummaryDirectory = "/$main::SyssummDirectory/$Target";
	print $DEBUGLOG "\tPath to summary directory is $PathToSummaryDirectory...\n" if $Debug;

	# include the files
	foreach $FileName ( @FilesToInclude ) {

		# is it an image?
		my $ImageFile = 0;
		foreach $Pattern ( @GraphicFileSuffixes ) {
			if ( $FileName =~ /\.$Pattern$/ ) {
				$ImageFile = 1;
				last;
			}
		}

		if ( $ImageFile ) {
			print $DEBUGLOG "\t\tincluding image $FileName...\n" if $Debug;
			print "<img src=$PathToSummaryDirectory/$FileName>\n";
		}
		else {
			print $DEBUGLOG "\t\tincluding $FileName...\n" if $Debug;
			open INPUT, "<$FileName" or
				print "Couldn't open file $FileName for inline inclusion: $!";
			my @Input = <INPUT>;
			print "@Input";
			close(INPUT);	
		}
	}


	return;
}


########################## Utility  ########################## 

# borrowed from ugsearch.pl (DESC)
# originally derived from _CGI Programming_, Gundavaram, pp 66-67

sub ReturnError ($$$)
{	my $Status = shift;
	my $Keyword = shift;
	my $Message = shift;

	GenerateHeader("sysquery.pl - Summary Not Found");

	print "Status: $Status $Keyword\n\n";
	print "<h1> $Keyword </h1>\n";
	print "$Message<br>\n";

	GenerateFooter();

	exit 1
}


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

# don't buffer the output
STDOUT->autoflush(1);

ParseInputFromWebServer();

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