#!/usr/bin/perl -w
#
# syssumm.pl - Summarize the system the script is running on
#
# Synopsis:
#
#	syssumm.pl [ -C cat ] [ -d ] [ -f fname ] [ -m mailaddr ] \
# 		[ -s smtphost ] [ -u username ] [ -v ] [ -X cat ]
#
# The purpose of this Perl script is to automate the collection
# of information about the hardware, software, and network 
# configuration of a given UNIX system.
# 
# The "-d" command line argument turns on debugging output
# which is written to STDERR.  The "-f fname" command line
# argument specifies an output file different from the default
# ("./syssumm.out").  The "-m mailaddr" specifies where to mail
# the output of the program.  If this option is not specified,
# then the output is left in the file and not e-mailed off
# system.  The "-v" verbose option provides some brief messages
# about the progress of the program during the information
# collection process.  It is not related to the debugging 
# output.
# 
# Using the e-mail command line options (-m, -s, -u): syssumm
# uses a Perl module called Sendmail.pm to deliver the message
# to the web server.  Normally, the only command line option 
# you would ever need to use to deliver the message to the web
# server is the "-m" command line option:
# 
#	syssumm ... -m "syssumm@web.server.dom" ...
# 
# where the address is to the syssumm acount (usually "syssumm")
# on the web server.
# 
# However, there may be some networks were the webserver e-mail
# is delivered via an MX record.  The Sendmail.pm module does
# *NOT* support MX processing, so it would blindly try to 
# deliver the message to the server portion of the e-mail address
# specified by the "-m" command line option.  You can over-ride
# this and "emulate" MX processing with the "-s" command line 
# option:
# 
#	syssumm ... -m "syssumm@web.server.dom" \
#		-s postoffice.other.dom ...
# 
# In this example, the message for "syssumm@web.server.dom" would
# actually be delivered to the "postoffice.other.dom" server
# for actual delivery.
# 
# Note that the code that drives the Sendmail.pm module will try
# to deliver the message to the following servers (in this order):
# 
#	SMTP host (if specified via "-s" option)
#	localhost (if email available)
#	host portion of the e-mail address (if SMTP 
#				host *NOT* specified)
# 
# The -u command line option defines the userid to be used for
# the From: field in the e-mail headers of the profile as it is
# mailed to the web server system.  It is for systems (like WinNT)
# where the userid may not be automatically determined.
# 
# The Sendmail.pm module was located by Frank Crawford and 
# most of the logic of this section came directly from him.  
# Thanks, Frank!
# 
# The -C and -X command line options are a contribution from
# Jeff Putsch and allow testing of one or more categories
# without having to change the source code.  Here is his
# description:
# 
# JP In addition I changed syssumm.pl so it doesn't need to be 
# JP edited to turn on and off different categories while debugging. 
# JP I added a -C switch to turn on categories and a -X switch to 
# JP turn off categores. I made them mutually exclusive.
# JP 
# JP 	For example:
# JP 
# JP 		# run all BUT Hardware and Network categories
# JP 		syssumm.pl -X Hardware,Network               
# JP 
# JP 		# run only Software categories
# JP 		syssumm.pl -C Software        
# JP 
# JP The default is to run all categories. Again this is a hack, I 
# JP should be setting the default behavior for -C based upon the 
# JP Category hash, but I hard-coded it for now.
#
# Note that when syssumm.pl is running, there are really 3
# levels of script running:
#
#	syssumm.pl  ->  {OS}.pm  ->  common.pm
# 
# Once syssumm.pl determines which operating system is running
# on the local machine, it calls a routine in the Perl module
# for that system.  That OS-specific module defines things
# that are peculiar for that OS (and version).  It then makes
# call (where practical) into the common.pm to retrieve 
# information.
# 
# The information that is retrieved is stored in "category
# tables".  Each category ("general", "hardware", "software",
# "network", etc.) has it's own table of accumulated information.
# 
# Some of this information is derived from the local machine.
# Some of it is merely place holders for fields that must be
# supplied by some omniscient human system administrator.
# 
# The category tables are stored at the syssumm.pl (ie., "main::")
# level.  When common.pm learns something about the system, it
# pushes it onto the top of the category table in the top level.
# Finally, all of the category tables are written to a file.
# 
# It should write it's output in such a way that it could be 
# fed to a separate program which would generate web page output.
# 
# See the "use" statements below to determine which operating
# systems this script works with.
#
# Note that any existing OS Perl modules must be present on the
# machine (even if it's for an OS different from the running
# locally).  See the "use" statements below for what needs to be
# present.
# 
# The original idea for this script (and the others for creating
# the web page output) came from a conversation with Dave Abramson
# the week of 5 Oct 1998.  The format for the profile is derived
# from a MS Word document that I created in July of 1998.  Certain
# deep hardware oriented routines might be derived from a program 
# called sysinfo which is distributed by MagniComp Systems.  Russ
# Allbery provided the pointer to this program.
#
# The following individuals who participate in the comp.lang.perl.misc
# newsgroup contributed to improving the structure of this program:
# 
#	Brand Hilton bhilton@tsg.adc.com
# 	Rick Delaney rick.delaney@shaw.wave.ca
# 
# and I'm grateful for their input!
# 
# ==============================================================
# 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 Jim Holthaus
#	Copyright (c) 1999, 2000 Alek O. Komarnitsky
#	Copyright (c) 1999, 2000 Keong Lim
#	Copyright (c) 1999, 2000 Wanderley O. Mendes
#	Copyright (c) 1999, 2000 Jeff Putsch
#	Copyright (c) 1999, 2000 Brett M Hogden
# 
# ==============================================================
# 
# $Header: /export/home/mohlerb/syssumm/remote/RCS/syssumm.pl,v 1.44 2000/06/14 16:33:13 mohlerb Exp mohlerb $
# 
# $Revision: 1.44 $
#
# $Log: syssumm.pl,v $
# Revision 1.44  2000/06/14 16:33:13  mohlerb
# changes from Bruce Mohler including:
# added BackupProcedure field
#
# Revision 1.43  2000/04/27 18:10:02  mohlerb
# changes from Bruce Mohler including:
# added VendorSerialNumber and AssetTag to General category
#
# Revision 1.42  2000/04/27 17:21:11  mohlerb
# changes from Frank Crawford including:
# removed some extra lines
#
# Revision 1.41  2000/04/05 14:17:01  mohlerb
# changes from Frank Crawford including:
# modify the PATH environment variable, so we can find any
# helper programs as in ReadSunDisklabel.pl
#
# Revision 1.40  2000/04/04 02:26:21  mohlerb
# changes from Frank Crawford including:
# automatically generate the appropriate 'use lib' statement
#
# Revision 1.39  2000/02/10 18:56:33  mohlerb
# changes from Bruce Mohler including:
# added code to process an optional syssumm_site.pm file
# containing local site specific functions to be added to
# a profile; note that a syssumm_site-template.pm file has
# been added to the MANIFEST; just rename this file and add
# your functions; be sure to add entries into the syssumm.format
# file on your web server so that they will be displayed
#
# Revision 1.38  2000/02/09 23:44:18  mohlerb
# changes from Bruce Mohler including:
# cleaned up ReturnServices by moving function to common.pm
# and having it be driven by a @Services array in syssumm.pl;
# note also the option at the end of the Initialize<OS> function
# where you can add OS-specific services that you wish to have
# monitored
#
# Revision 1.37  2000/02/04 00:14:32  mohlerb
# changes from Bruce Mohler including:
# implemented ReturnServices to return all of the Running*
# functions as a multiple value field
#
# Revision 1.36  2000/02/03 23:22:02  mohlerb
# changes from Martin Andrews including:
# IRIX, Linux, and SunOS now report swap file sizes, stubs added
# to other OS modules
#
# Revision 1.35  2000/01/21 20:59:08  mohlerb
# changes from Bruce Mohler including:
# changed name of Microsoft Windows module in OSModule hash,
# s/Win32/MSWin32/ to match name of existing module (template);
# Gary Weimer <weimer@bis.kodak.com> was kind enough to notice
# this discrepancy
#
# Revision 1.34  2000/01/15 00:25:19  mohlerb
# changes from Jim Holthaus including:
# fix to help perl keep multiple value fields in the original
# order that it was sent to the web server; note that Jim has
# one server with over 300 printers; since four digits should
# be enough, we decided to bump it up another magnitude to 5
# digits
#
# Revision 1.33  2000/01/12 18:19:49  mohlerb
# changes from Frank Crawford including:
# 2 lines to support new BSD module
#
# Revision 1.32  2000/01/12 15:43:42  mohlerb
# changes from Frank Crawford including:
# patch to syssumm.pl to handle non-existant getpwuid/getlogin
# on various systems
#
# Revision 1.31  2000/01/12 01:19:35  mohlerb
# changes from Frank Crawford including:
# mostly changes to comments but a major fix to a typo in
# MailOutput
#
# Revision 1.30  2000/01/07 18:37:39  mohlerb
# changes from Bruce Mohler (and Frank Crawford) including:
# nearly final (?) changes for e-mail delivery after more discussion
#
# Revision 1.29  2000/01/06 19:20:40  mohlerb
# changes from Bruce Mohler including
# further final (?) changes for e-mail delivery after more testing
#
# Revision 1.28  2000/01/06 18:20:56  mohlerb
# changes from Bruce Mohler including:
# final (?) changes for e-mail delivery
#
# Revision 1.27  2000/01/06 16:56:04  mohlerb
# changes from Frank Crawford including:
# Taken $EmailAvailable out of common.pm, syssumm.pl and
# IRIX.pm; Made Sendmail.pm a package of its own; Added a
# Response variable in Sendmail.pm; Reworked error messages,
# etc, in common.pm; Fixed up the end of line in the messages;
# Reworked $Username as discussed
#
# Revision 1.26  2000/01/04 01:15:38  mohlerb
# changes from Bruce Mohler including:
# code mods to MailOutput function and related variables in
# syssumm.pl during testing of new e-mail module
#
# Revision 1.25  2000/01/03 20:11:21  mohlerb
# changes from Frank Crawford including:
# changes syssumm.pl and common.pm to support SMTP delivery
# of the report.  This second one requires a support file (included).
#
# Revision 1.24  1999/10/27 20:03:38  mohlerb
# changes from Alek O. Komarnitsky including:
# added ReturnFstab() to common.pm and stubs as appropriate
#
# Revision 1.23  1999/10/26 22:11:53  mohlerb
# Changes from Alek O. Komarnitsky including:
# added a stub routine for listing the file systems exported from
# the local machine
#
# Revision 1.22  1999/10/26 21:51:39  mohlerb
# Changes from Alek O. Komarnitsky including:
# added a stub routine for dumping the EEPROM for disaster
# recovery purposes
#
# Revision 1.21  1999/10/12 18:02:22  mohlerb
# changes from Bruce Mohler including:
# added BootupProcedure and ShutdownProcedure as protected fields
#
# Revision 1.20  1999/08/30 14:56:50  mohlerb
# changes from Wanderley O. Mendes including:
# changes for new OSF1.pm module
#
# Revision 1.19  1999/08/07 03:21:04  mohlerb
# changes from Paul Farrall including:
# changed default path to Perl to /usr/bin
#
# Revision 1.18  1999/08/06 02:35:36  mohlerb
# changes from Bruce Mohler including:
# minor change to make debugging easier
#
# Revision 1.17  1999/07/02 21:21:15  mohlerb
# changes from Bruce Mohler including:
# Perl complained about the UnameCommand in syssumm.pl not being
# used because it couldn't see (yet) the references down in the
# OS modules.  Since AIX doesn't have the uname command in the
# same place as the other OSs (so far), I moved the UnameCommand
# local to each OS module.
#
# Revision 1.16  1999/06/10 22:42:37  mohlerb
# changes from Bruce Mohler including:
# added code around the eval("use OS") call to assist in debugging
#
# Revision 1.15  1999/06/10 21:58:01  mohlerb
# changes from Bruce Mohler including:
# standardized derivation of NodeName across OS modules:
# it is defined near the beginning of the Initialize${OSName}
# and stored in $main::NodeName.  The ReturnNodeNameField
# merely returns $main::NodeName.
#
# Revision 1.14  1999/06/08 17:46:01  mohlerb
# changes from Frank Crawford including:
# Change $NodeName setting to invoke ${OS}.pm routine (if declared).
# Move setting of $FullyQualifiedDomainName to common.pm.
# Fix a comment to more accurately reflect the usage now.
#
# Revision 1.13  1999/06/07 18:56:13  mohlerb
# change from Bruce Mohler including:
# common ReturnRunAsRoot relocated from individual OS modules
# to common.pm; RunAsRoot variable relocated to syssumm.pl
# (referenced as $main::RunAsRoot); this suggestion was really
# Frank Crawford's...
#
# Revision 1.12  1999/06/04 18:51:51  mohlerb
# changes from Bruce Mohler including:
# Added code for "RunAsRoot" functionality.
# Added code to process Comments category last.
#
# Revision 1.11  1999/06/03 20:05:23  mohlerb
# changes from Jeff Putsch including:
# Added hooks for "Default" IP from Paul Farrall.
# These are needed by the update to SunOS.pm.
#
# Revision 1.10  1999/05/30 19:24:29  mohlerb
# changes from Frank Crawford including:
# Changed the OS selection from multiple if statements to a hash lookup.
#
# Revision 1.9  1999/05/27 14:30:59  mohlerb
# changes from Frank Crawford including:
# Disable debugging (can be turned back on with -d argument).
#
# Revision 1.8  1999/05/27 14:09:57  mohlerb
# changes from Keong Lim including:
# added Header RCS tag above Revision tag
#
# Revision 1.7  1999/05/26 20:47:15  mohlerb
# changes from Brett M Hogden including:
# Added initial AIX support.
#
# Revision 1.6  1999/05/25 20:18:43  mohlerb
# changes from Jeff Putsch including:
# Added command line options:
#   -C   list of categories to process
#   -X   list of categories to exclude
#
# Revision 1.5  1999/05/25 18:57:07  mohlerb
# changes from Jeff Putsch including:
# Added initial SunOS support.
#
# Revision 1.4  1999/05/25 17:56:45  mohlerb
# changes from Frank Crawford including:
# Added use of standard Perl5 module Sys::hostname from common.pm.
#
# Revision 1.3  1999/05/24 23:57:38  mohlerb
# changes from Frank Crawford including:
# Use FileHandle instead of IO:Handle as backward portable.
#
# Revision 1.2  1999/05/24 23:54:44  mohlerb
# changes from Frank Crawford including:
# Added LastUpdate field
# Fixed up call to MailOutput
# Took out some other debugging code
# Set value for FullQualifiedDomainName
#
# Revision 1.1  1999/05/14 22:58:05  mohlerb
# Initial revision
#

require 5.003;

# Set up some environment needed later.
BEGIN {
	my $dirname;
	($dirname = $0) =~ s:/[^/]*$::;
	# The following is equivalent to "use lib $dirname;" 
	# but can't do use in BEGIN
	unshift(@INC, $dirname);
	# Set up for some of our helper programs.
	$ENV{'PATH'} = $dirname . ':' . $ENV{'PATH'};
}

use Getopt::Std;
use FileHandle;
use Sys::Hostname;
# more "use" statements below in FigureOutWhatOSWereRunning()
use common;

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

# set by command line args
$Debug = 0;
$DefaultRecipient = "syssumm";			# e-mail To: ???@SMTPHost
$EmailAddress = "";				# e-mail To:
$EmailAvailable = 0;				# set in ReturnRunningSendmail*
$SMTPHost = "";					# host to send message to
$Username = "";					# e-mail From:
$OutputFile = "syssumm.out";
$Verbose = 0;

$EmailMessage = "";
$NodeName = "unknown";
$SiteModuleName = "syssumm_site";

# comments
$RunAsRoot = 0;					# set in OS modules; read in common.pm


###########################################################################

# The following hash maps the value in $^O to a 
# specific Perl module for that operating system.

%OSModule = (
	'aix' => 'AIX',
	'bsdos' => 'BSD',
	'freebsd' => 'BSD',
	'dec_osf' => 'OSF1',
	'hpux' => 'HPUX',
	'irix' => 'IRIX',
	'linux' => 'Linux',
	'MSWin32' => 'MSWin32',
	'solaris' => 'SunOS',
);

$OSName = "";


# The following hash defines the categories and subcategories of the
# information that will be generated.  Note that because the keys are
# all hashed, you cannot count on what order things will be called.
# Because of this, there is special handling to force the "Comments"
# category to be processed last.
# 
# The value of $Routines{$Category}{$Subcategory} is a reference to
# a function.  Each function will be handed a single argument which 
# is the subcategory and will return an array.  If there is a single 
# element in the array, then a single line will be appended to the 
# output file.  If there are 2 or more elements in the array, then 
# each label will be suffixed for uniqueness.  The initialization 
# function for each OS and version will override some of the functions 
# specified below.

%Routines = (

	General => {
		NodeName => \&ReturnNodeNameField,
		Organization => \&ReturnProtectedField,
		Vendor => \&ReturnUnknownField,
		Model => \&ReturnUnknownField,
		HostId => \&ReturnUnknownField,
		SystemHandle => \&ReturnProtectedField,
		VendorSerialNumber => \&ReturnProtectedField,
		AssetTag => \&ReturnProtectedField,
		Location => \&ReturnProtectedField,
		DateSystemInstalled => \&ReturnProtectedField,
		LargerPicture => \&ReturnProtectedField,
		LastUpdate => \&ReturnCtimeField,
	},

	Hardware => {
		Processors => \&ReturnUnknownField,
		Memory => \&ReturnUnknownField,
		Disks => \&ReturnUnknownField,
		Tapes => \&ReturnUnknownField,
		StorageManager => \&ReturnUnknownField,
		Console => \&ReturnProtectedField,
		OtherPeripherals => \&ReturnProtectedField,
		Eeprom => \&ReturnUnknownField,
	},

	Network => {
		DomainName => \&ReturnUnknownField,
		DefaultRouter => \&ReturnUnknownField,
		NameServer => \&ReturnUnknownField,
		IPAddress => \&ReturnUnknownField,
		SubnetMask => \&ReturnUnknownField,
		NetworkInterfaces => \&ReturnUnknownField,
	},

	Software => {
		OsName => \&ReturnUnknownField,
		OsVersion => \&ReturnUnknownField,
		NbrOfLicenses => \&ReturnUnknownField,
		Patches => \&ReturnUnknownField,
		Printers => \&ReturnUnknownField,
		Services => \&ReturnUnknownField,
		IPAddressResolution => \&ReturnUnknownField,
		GraphicalUserInterface => \&ReturnUnknownField,
		FontServer => \&ReturnUnknownField,
		Fstab => \&ReturnUnknownField,
		LocalFileSystems => \&ReturnUnknownField,
		RemoteFileSystems => \&ReturnUnknownField,
		ExportedFileSystems => \&ReturnUnknownField,
		Swap => \&ReturnUnknownField,
		ApplicationsInstalled => \&ReturnProtectedField,
		InstallationProcedure => \&ReturnProtectedField,
		BootupProcedure => \&ReturnProtectedField,
		ShutdownProcedure => \&ReturnProtectedField,
		BackupProcedure => \&ReturnProtectedField,
		DataFlowsTo => \&ReturnProtectedField,
		DataFlowsFrom => \&ReturnProtectedField,
	},

	Site => {
		# routines will be added by the InitializeSite function
	},

	Comments => {
		RunAsRoot => \&ReturnRunAsRoot,
	},
);


# The following hash defines the universal services that we'll
# look for on each system to see whether they are running or not.

@Services = (
	"Lpsched",
	"Sendmail",
	"NFS",
	"Automount",
	"NIS",
	"Nameserver",
	"Samba",
	"Accounting",

	# 
	# Deferred items:
	# 
	#	UUCP
	#	NetNews
	# 
);


$OnlyTheseCategories = "";
$ExceptTheseCategories = "";


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

sub ProcessCommandLine
{
	# initialize categories
	foreach $Category (keys %Routines) {
		if ( $OnlyTheseCategories eq "" ) {
			$OnlyTheseCategories = $Category;
		}
		else {
			$OnlyTheseCategories = "$OnlyTheseCategories,$Category";
		}
	}

	if ( getopts('C:df:m:s:u:vX:') ) {
		if ( $opt_C ) { $OnlyTheseCategories = $opt_C; }
		if ( $opt_d ) { $Debug = $opt_d; }
		if ( $opt_f ) { $OutputFile = $opt_f; }
		if ( $opt_m ) { $EmailAddress = $opt_m; }
		if ( $opt_s ) { $SMTPHost = $opt_s; }
		if ( $opt_u ) { $Username = $opt_u; }
		if ( $opt_v ) { $Verbose = $opt_v; }
		if ( $opt_X ) { $ExceptTheseCategories = $opt_X; }
	}
	else {
		die "There were problems processing the command line.";
	}

	# if user specified e-mail option, then validate
	$EmailAvailable = 0;
	if ( $EmailAddress ) {

		# 
		# Come to think of it, if this were an old style
		# UUCP address, this next section would fail
		# 

		# make sure userid specified
		if ( $EmailAddress =~ /\@/ ) {
			my ( $UseridPart, $HostPart ) = 
				split('\@', $EmailAddress);
			if ( ( ! $UseridPart ) or ( ! $HostPart ) ) {
				die "$EmailAddress is an invalid e-mail address.";
			}
		}
		else {
			# prepend userid $DefaultRecipient
			$EmailAddress = $DefaultRecipient . '@' . $EmailAddress;
		}
	}

	# make sure $Username is valid
	if ( ! $Username ) {
		# note that getpwuid() will fail under Win32
		$Username = eval { getlogin() } || eval { getpwuid($<) } || "root";
	}

	# -C and -X options are mutually exclusive
	if ( $opt_C and $opt_X ) {
		die "Do not use -C and -X options together";
	}

	return;
}


sub FigureOutWhatOSWereRunning
{
	print "FigureOutWhatOSWereRunning()...\n" if $Debug;

	# what is the name of the OS?
	$OSName = $^O;
	print "\tRunning $OSName...\n" if $Debug;

	if ( defined($OSModule{$OSName}) ) {
		$OSName = $OSModule{$OSName};
		print "\tLoading $OSName module\n" if $Debug;
		eval("use $OSName");
		if ( $@ ) {
			print STDERR "Eval failed: $@";
			print STDERR "An OS module for $OSName was not found\n";
			exit 1;
		}
		else {
			&{ "Initialize${OSName}" }();

			# is there a syssumm-site module to process?
			eval("use $SiteModuleName");
			if ( $@ ) {
				print "No local site module to process...\n" if $Debug;
			}
			else {
				&{ "InitializeSite" }();
			}

			return;
		}
	}

	# if we get here, we have an (currently) unsupported OS
	print STDERR "This program does not support $OSName\n";
	exit 1;
}


sub ProcessAllCategories
{
	print "ProcessAllCategories()...\n" if $Debug;

	# open the output file
	open OUTPUT, ">$OutputFile" or
		die "Can't open $OutputFile for output: $!";

	foreach $Category (keys %Routines) {

		# XXX DEBUGGING
		next if ( $ExceptTheseCategories =~ /$Category/ );
		next unless ( $OnlyTheseCategories =~ /$Category/ );

		# process comments after all of the other categories have completed
		# save for last - we'll process below
		next if ( $Category eq "Comments" );

		ProcessThisCategory($Category);
	}

	# now process the comments category
	ProcessThisCategory("Comments");

	# close the output file
	close(OUTPUT);

	return;
}


sub ProcessThisCategory
{	my $Category = shift;

	print "\nProcessing category $Category...\n" if $Verbose;

	foreach $Subcategory (keys %{ $Routines{$Category} }) {
		my @Response = ( );

		print "\nProcessing subcategory $Subcategory...\n" if $Verbose;
		# call the function to collect the information
		@Response = &{$Routines{$Category}{$Subcategory}}($Subcategory);
		print "\treturned @Response\n" if $Debug;

		# ignore this subcategory?
		next if ( $Response[0] eq "IGNORE" );

		# append it/them to the output file and save for e-mail message
		if ( scalar(@Response) < 2 ) {
			my $Line = "$Category:$Subcategory:$Response[0]\n";
			print OUTPUT $Line;
			$EmailMessage .= $Line;
		}
		else {	# multiple responses
			# we're using a zero filled suffix to help
			# perl sort our multiple field entries
			# into the correct order...
			my $Suffix = "00000";
			foreach $Response (@Response) {
				next if $Response =~ /$ForceMultipleResponseTag/;
				my $Line = "$Category:$Subcategory-$Suffix:$Response\n";
				print OUTPUT $Line;
				$EmailMessage .= $Line;
				$Suffix++;
			}
		}
	}

	return;
}


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

# strip path off of script name
$0 = (@discard = split(/\//, $0))[$#discard];

STDOUT->autoflush(1);			# from Perl Cookbook section 7.12

ProcessCommandLine();
FigureOutWhatOSWereRunning();
ProcessAllCategories();
MailOutput();				# in common.pm

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

