#!/usr/bin/perl
#
# install.pl - Install the web server scripts
# 
# Synopsis:
# 
#	install.pl [ -d ] [ -f ] [ -h ]
# 
# This script
# 
#	Makes sure we're running as root (you can force *not* running as root).
# 
#	Makes sure all required files are in current working directory.
# 
#	Makes sure syssumm account is setup properly.
# 
#	Copies incoming.pl to syssumm home directory and makes sure
# 	.forward file exists.
# 
#	Checks for web server configuration file in syssumm home
# 	directory and creates it if it doesn't exist.  This file
# 	contains path to CGI directory and base of Root of directory
# 	tree for HTML documents.
# 
#	It copies sysquery.pl into the CGI directory.
# 
#	It make sure that the syssumm sub-directory exists under
#	HTML root directory.  If syssumm.format doesn't exist,
#	it copies that file into the syssumm sub-directory otherwise
#	it prompts the user to let him/her know that the file
#	exists but may have local modifications.  Ditto for the
# 	syssumm.header file (it will not overwrite an existing
# 	file).
# 
# This script assumes that we're running under a UNIX-like
# operating system.
# 
# The idea for this script was suggested by Bart Swennen:
# 
# BS would it be possible to put all things that must be customized into a
# BS separate file and to distribute only a sample for this file, but not the
# BS file itself in the sources ? In this way, users can put their own
# BS customizations in that file, starting with the sample file as an
# BS example. When a new release arrives, you just have to unpack it and
# BS leave the customization file as you edited it before. Because it is not
# BS in the source distribution, it will not be overwritten by unpacking the
# BS new release.
# 
# ==============================================================
# 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) 1999, 2000 Martin Andrews
# 
# ==============================================================
# 
# $Header: /export/home/mohlerb/syssumm/web.server/RCS/install.pl,v 1.19 2000/05/04 21:17:56 mohlerb Exp mohlerb $
# 
# $Revision: 1.19 $
# 
# $Log: install.pl,v $
# Revision 1.19  2000/05/04 21:17:56  mohlerb
# changes from Bruce Mohler including:
# check for a file ".ran.install" left behind by the install
# shell script; if the file doesn't exist, then quit with an
# appropriate comment
#
# Revision 1.18  2000/04/17 04:37:46  mohlerb
# changes from Bruce Mohler including:
# removed reference to obsolete function CreateConfigurationFile;
# this defect was found by Michael Seeger <michael.seeger@mchh.siemens.de>
#
# Revision 1.17  2000/04/04 03:29:40  mohlerb
# changes from Frank Crawford including:
# added copy of setfield.pl and use of current perl executable,
# rather than a statically defined one
#
# Revision 1.16  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.15  2000/03/03 00:08:58  mohlerb
# changes from Bruce Mohler including:
# restructed code to make script more modular; added @Comments
# which functions can add text to; if @Comments contains any
# lines, they are displayed at the end of the program so that
# the system administrator can see things that are really
# important (like they need to update their syssumm.format
# file)
#
# Revision 1.14  1999/10/20 20:07:57  mohlerb
# changes from Bruce Mohler including:
# support for $DOCROOT/syssumm/syssumm.header file for customizing
# web pages at your local site
#
# Revision 1.13  1999/08/11 20:28:11  mohlerb
# changes from Paul Farrall including:
# Replaced subroutine CopyFile() with Perl library function copy()
# in File::Copy package
# Added some lines to check for existing $PATH.  If none is set,
# then set it to /bin:/usr/bin.  Otherwise append /bin:/usr/bin,
# but don't nuke what's already there
# Added SearchForPerl() subroutine.  All syssumm scripts look
# for Perl in /usr/bin.  Since users may have Perl installed
# somewhere else, I thought it would be nice if we check that
# for the user
# If it finds it in some other place than /usr/bin it makes
# symbolic link /usr/bin/perl -> path_to_perl.  If it can't find
# perl interpreter, it displays a warning message, but doesn't
# stop script execution
#
# Revision 1.12  1999/08/06 21:54:49  mohlerb
# changes from Paul Farrall including:
# changed default path to Perl to /usr/bin
# changed install.pl to make a backup of any existing version of
# sysquery.pl before copying over it
# did the same for incoming.pl, common.pm, and updatelists.pl
# added copy of updatelists.pl to syssumm home directory
# fixed error on setting file permissions on format file
# changed .forward install to print "installed .forward..." OR
# ".forward found..."
#
# Revision 1.11  1999/08/04 17:03:34  mohlerb
# changes from Bruce Mohler including:
# when the syssumm home directory and web.server.info files are
# created, make sure that they are created with permissions that
# make them globally readable, otherwise processes like the
# sysquery.pl script (running under the web server account) may
# not get access to the common.pm and configuration files.
#
# Revision 1.10  1999/08/04 03:59:02  mohlerb
# changes from Bruce Mohler including:
# added a little more explanatory information at the point where
# the script stops to let you create the syssumm account
#
# Revision 1.9  1999/08/04 00:29:01  mohlerb
# changes from Bruce Mohler including:
# the function that checks to make sure that MANIFEST is all
# present properly tests for directories now, not just files
#
# Revision 1.8  1999/07/02 21:59:26  mohlerb
# changes from Bruce Mohler including:
# fixes after testing sysquery.pl
#
# Revision 1.7  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.6  1999/06/22 19:25:45  mohlerb
# changes from Bruce Mohler including:
# added $SyssummUserid to the config file so that the value is
# persistent across installations
#
# Revision 1.5  1999/06/18 21:20:38  mohlerb
# changes from Frank Crawford and Bruce Mohler including:
# Frank: Addition of command line option to specify SysSumm user.
# Frank: Various bug fixes.
# Bruce: Check each config file entry to make sure that it's in
# Bruce: the correct order.
# Bruce: Fix hard-coded source for processing entries into and
# Bruce: out of the configuration file.  Driven by 1 array and
# Bruce: 2 hashes.
#
# Revision 1.4  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.3  1999/06/16 16:04:21  mohlerb
# changes from Martin Andrews including:
# fixes for routines prompting for information
#
# Revision 1.2  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.1  1999/06/04 22:56:18  mohlerb
# Initial revision
#

use FileHandle;
use File::Copy;  # includes copy function for copying files
use Getopt::Std;
use common;

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

# switches set on the command line
$Debug = 0;
$DisplayHelpAndQuit = 0;
$RunAnyway = 0;

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

$ManifestFileName = "MANIFEST";

# common Perl module
$CommonPerlModule = "common.pm";

# e-mail information
$ScriptPermissions = 0755;
$FilePermissions = 0644;
$IncomingScriptName = "incoming.pl";
$ForwardFileName = ".forward";
$UpdateListsScriptName = "updatelists.pl";
$SetFieldScriptName = "setfield.pl";

# web server information
$DirectoryPermissions = 0755;

$DefaultPathToPerl = $^X;
$AbsoluteDefaultPathToPerl = "/usr/bin/perl";

@Comments = ( );


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

############################ Main Functions ###########################

sub ProcessCommandLine
{
	if ( -f ".ran.install" ) {
		unlink ".ran.install";
	}
	else {
		print STDERR "You need to run \"./install\" rather than \"./install.pl\".\n";
		print STDERR "Exiting...\n";
		exit 1;
	}

	if ( getopts('dfh') ) {
		if ( $opt_d ) { $Debug = 1; }
		if ( $opt_f ) { $RunAnyway = 1; }
		if ( $opt_h ) { $DisplayHelpAndQuit = 1; }
	}
	else {
		die "There were problems processing the command line.";
	}

	if ( $DisplayHelpAndQuit ) {
		DisplayHelp();
		exit(0);
	}

	return;
}


#######################################################################
# Initialization
#######################################################################

sub InitializeStuff
{
	RunAsRoot();
	SearchForPerl();
	CheckManifest();
	CheckForConfigFile();

	return;
}


sub RunAsRoot
{
	if ( $< != 0 ) {		# running as uid 0 (zero)?
		print STDERR "You're not running as root ";
		if ( $RunAnyway ) {
			print STDERR "but I guess you know what you're doing...\n\n";
		}
		else {
			print STDERR "so I'm going to terminate...\n";
			print STDERR "If you're really want to run this script ";
			print STDERR "as someone other than root,";
			print STDERR "use the \'-f\' command line option\n";
			exit(1);
		}
	}

	# let's set the path just in case...
	# But don't nuke existing path if it exists

	if ( $ENV{PATH} ) {
		$ENV{"PATH"} = "$ENV{PATH}" . ":/bin:/usr/bin";
	} else {
		$ENV{"PATH"} = "/bin:/usr/bin";
	}

	return;
}


sub SearchForPerl()
{
	# All syssumm scripts look for perl in $DefaultPathToPerl, so
	# check if it's there.  If not, find it and add symbolic link.

	# Make sure that $DefaultPathToPerl is an absolute
	# path.  If not, default to /usr/bin...
	if ( ! ( "$DefaultPathToPerl" =~ /^\// ) ) {
		# relative path
		$DefaultPathToPerl = $AbsoluteDefaultPathToPerl;
	}

	if ( ! ( -f "$DefaultPathToPerl")) {
		print "Looks like the perl interpreter is not in /usr/bin\n";
		print "Please wait while I look for it...\n";

		my $FoundIt = 0;
		foreach $Directory ( split(/:/, $ENV{"PATH"}) ) {
			print "Looking for perl in $Directory.....";
			if ( -f "$Directory/perl" ) {
				print "Found it!\nAdding symlink $Directory/perl -> $DefaultPathToPerl\n";
				$FoundIt = 1;
				symlink("$Directory/perl", "$DefaultPathToPerl") or
					warn "Couldn't create symlink: $!";
				my $Comment = "Created symlink $Directory/perl /usr/bin/perl";
				push @Comments, $Comment;
				last;
			}
			print "\n";
		}
		if ( ! $FoundIt ) {
			print STDERR "Couldn't find the perl executable in any\n";
			print STDERR "of the directories in your PATH variable.\n";
			print STDERR "Please create a symbolic link in /usr/bin\n";
			print STDERR "to your perl executable.\n";
			exit(1);
		}
	}
	else {
		print "Found perl in $DefaultPathToPerl\n";
	}

	return 0;
}


sub CheckManifest
{
	# does the Manifest file exist in the current directory?
	if ( ! -f $ManifestFileName ) {
		print STDERR "I cannot find the $ManifestFileName file in the current directory\n";
		print STDERR "This script assumes that you've un-tar'd all of the\n";
		print STDERR "files for the web server into the current directory.\n";
		exit(1);
	}

	# how "fresh" is the file?
	if ( ( -M $ManifestFileName ) > 30 ) {
		print STDERR "The $ManifestFileName file is over 30 days old.\n";
		print STDERR "It may be stale!\n";
		if ( $RunAnyway ) {
			print STDERR "Since you're running with '-f', I'll continue.\n";
		}
		else {
			exit(1);
		}
	}

	# load the file and make sure all of the files are present
	open MANIFEST, "<$ManifestFileName" or
		die "Coulnd't open $ManifestFileName file: $!";
	my @ListOfFiles = <MANIFEST>;
	chomp(@ListOfFiles);
	close MANIFEST;

	my $NotFound = 0;
	foreach $File (@ListOfFiles) {
		if ( ! ( -f $File or -d $File )) {
			print STDERR "Didn't find file/directory: $File\n";
			$NotFound = 1;
		}
	}
	if ( $NotFound ) {
		print STDERR "One or more required files in the $ManifestFileName file were not found.\n";
		exit(1);
	}

	print "All required files are present.\n\n";

	return;
}


sub CheckForConfigFile
{
	# does the account exist?
	my $HomeDirectory = (getpwnam($SyssummUserid))[7];
	if ( $HomeDirectory eq "" ) {
		print STDERR "No $SyssummUserid account exists.\n";
		print STDERR "If you are running the install script for the first time, please create\n";
		print STDERR "the account before you rerun this script.\n";
		print STDERR "For example, if you are running this script under Linux, you could:\n\n";
		print STDERR "useradd -g <gid> -u <uid> -m -n syssumm\n\n";
		print STDERR "Substitute a real uid and gid for the <uid>\n";
		print STDERR "and <gid> in the example.\n";
		exit(1);
	}

	# make sure the home directory exists
	if ( ! -d $HomeDirectory ) {
		print STDERR "The home directory for the $SyssummUserid account doesn't exist.\n";
		print STDERR "Please create the directory before you rerun the script.\n";
		exit(1);
	}
	print "A home directory for $SyssummUserid exists...\n\n";

	# make sure permissions on home directory are correct
	my $Mode = ( stat $HomeDirectory )[2];
	# we only want the permission bits...
	$Mode = $Mode & 0777;
	if ( $Mode != $DirectoryPermissions ) {
		print STDERR "The permissions on the home directory for the $SyssummUserid account\n";
		print STDERR "need to be changed to 0755.\n";
		exit(1);
	}

	# does the configuration file exist?
	my $PathToConfigFile = "$HomeDirectory/$ConfigurationFileName";
	LoadConfigurationFile(1);

	return;
}


#######################################################################
# Syssumm Account Validation
#######################################################################

sub CheckSyssummAccount
{
	CheckSyssummPasswd();
	CheckSyssummUid();

	return;
}


sub CheckSyssummPasswd
{
	# is it password protected?
	my $Password = (getpwnam($SyssummUserid))[1];
	if ( $Password eq "" ) {	# Nope.
		print STDERR "The $SyssummUserid account exists but doen't have a password.\n";
		print STDERR "This is a potential security problem.\n";
		print STDERR "Please put a password on the account before you rerun the script.\n";
		exit(1);
	}
	print "It is protected by a password...\n";

	return;
}


sub CheckSyssummUid
{
	# make sure that the uid is not 0 (zero)
	if ( $SyssummUid == 0 ) {		# Yep.
		print STDERR "The uid of the $SyssummUserid account is zero (ie., root)\n";
		print STDERR "This is a potential security problem.\n";
		print STDERR "Please change the uid before you rerun the script.\n";
		exit(1);
	}
	print "It has no special priveleges...\n";

	return;
}


#######################################################################
# Email Setup
#######################################################################

sub SetupEmail
{
	SetupEmailFiles();
	SetupEmailForwardFile();

	return;
}


sub SetupEmailFiles
{
	# 
	# XXX - This routine doesn't take smrsh-enabled systems into account
	# 

	foreach $File ( "$IncomingScriptName", "$CommonPerlModule", "$UpdateListsScriptName", "$SetFieldScriptName" ) {

		# Make an attempt to backup files if they already exist
		if ( -f "$SyssummHomeDirectory/$File" ) {
			rename ("$SyssummHomeDirectory/$File", "$SyssummHomeDirectory/$File.bak") ||
				die "can't back up $File\n";
			print "$File Exists (and was backed up)\n";
		}

		copy($File, $SyssummHomeDirectory);
	
		# make sure ownerships and permissions are correct
		my $PathToFile = "$SyssummHomeDirectory/$File";
		chown $SyssummUid, $SyssummGid, $PathToFile;
		if ( $File =~ /\.pl$/ ) {
			chmod $ScriptPermissions, $PathToFile;
		}
		else {
			chmod $FilePermissions, $PathToFile;
		}
	
		print "$File copied to $SyssummHomeDirectory\n";
	}

	return;
}


sub SetupEmailForwardFile
{
	# make sure that the .forward file exists
	my $PathToForwardFile = "$SyssummHomeDirectory/$ForwardFileName";
	my $PathToScript = "$SyssummHomeDirectory/$IncomingScriptName";
	if ( ! -f $PathToForwardFile ) {
		open FORWARD, (">$PathToForwardFile") or
			die "Couldn't create $PathToForwardFile: $!";
		print FORWARD "|$PathToScript\n";
		close FORWARD;

		# make sure ownerships and permissions are correct
		chown $SyssummUid, $SyssummGid, $PathToForwardFile;
		chmod $FilePermissions, $PathToForwardFile;

		print "$PathToForwardFile created\n";
	}
	else {
		print "A .forward file already exists...\n\n";
	}

	# 
	# One thing we might wnat to do is to check the permissions
	# on the .forward file *even* if it exists.  During one of
	# my testing sessions, I had the forwarding function fail
	# because the .forward file was owned by me and not syssumm.
	# 

	return;
}


#######################################################################
# Web Setup
#######################################################################

sub SetupWeb
{

	SetupCgiScript();

	my $PathToSyssummDirectory = "$DocrootDirectory/$SyssummDirectory";

	SetupSubDirectory($PathToSyssummDirectory);
	SetupFormatFile($PathToSyssummDirectory);
	SetupHeaderFile($PathToSyssummDirectory);

	return;
}


sub SetupCgiScript
{
	# move the (new) CGI script into CGI directory
	foreach $File ( "$CGIScriptName", ) {

		# Make an attempt to backup script if it already exists.
		if ( -f "$CGIDirectory/$CGIScriptName" ) {
			print "$CGIDirectory/$CGIScriptName Exists\n";
			rename ("$CGIDirectory/$CGIScriptName", "$CGIDirectory/$CGIScriptName.bak") ||
				die "can't back up $CGIDirectory/$CGIScriptName\n";
			print "$CGIDirectory/$CGIScriptName backed up\n";
		}

		copy($File, $CGIDirectory);
	
		# make sure ownerships and permissions are correct
		my $PathToFile = "$CGIDirectory/$File";
		chown $SyssummUid, $SyssummGid, $PathToFile;
		if ( $File =~ /\.pl$/ ) {
			chmod $ScriptPermissions, $PathToFile;
		}
		else {
			chmod $FilePermissions, $PathToFile;
		}
	
		print "$File copied to $CGIDirectory\n";
	}

	return;
}


sub SetupSubDirectory ($)
{	my $PathToSyssummDirectory = shift;

	# make sure syssumm sub-directory exists
	if ( ! -d $PathToSyssummDirectory ) {
		mkdir $PathToSyssummDirectory, $DirectoryPermissions or
			die "Couldn't create directory $PathToSyssummDirectory: $!";
		chown $SyssummUid, $SyssummGid, $PathToSyssummDirectory;
	}
	print "$PathToSyssummDirectory exists...\n";

	return;
}


sub SetupFormatFile ($)
{	my $PathToSyssummDirectory = shift;

	# check out the format file
	my $PathToFormatFile = "$PathToSyssummDirectory/$FormatFileName";
	# if it already exists...
	if ( -f $PathToFormatFile ) {
		# ... then move the prior one out of the way
		my $PathToFormatFileBackup = "$PathToFormatFile$PreviousFileSuffix";
		rename $PathToFormatFile, $PathToFormatFileBackup or
			die "Couldn't rename $PathToFormatFile: $!";

		push @Comments, "A prior format file was found and saved as";
		push @Comments, "$PathToFormatFileBackup.";
		push @Comments, "Hand merge any local changes into the new format file.";
	}

	copy($FormatFileName, $PathToSyssummDirectory);

	chown $SyssummUid, $SyssummGid, $PathToFormatFile;
	chmod $FilePermissions, $PathToFormatFile;

	print "$FormatFileName copied to $PathToSyssummDirectory...\n\n";

	return;
}


sub SetupHeaderFile ($)
{	my $PathToSyssummDirectory = shift;

	# check out the header file
	my $PathToHeaderFile = "$PathToSyssummDirectory/$HeaderFileName";
	if ( ! -f $PathToHeaderFile ) {
		# file doesn't exist
		copy($HeaderFileName, $PathToSyssummDirectory);

		chown $SyssummUid, $SyssummGid, $PathToHeaderFile;
		chmod $FilePermissions, $PathToHeaderFile;

		print "$HeaderFileName copied to $PathToSyssummDirectory...\n\n";
	}
	else {
		print "A header file already exists.\n";
	}

	return;
}


#######################################################################
# Utility Stuff
#######################################################################

sub PrintComments
{
	if ( @Comments ) {
		# mark this as beginning of special output
		print '*' x 76; print "\n";
		print '*' x 76; print "\n";

		foreach $Line (@Comments) {
			print "* $Line\n";
		}

		# mark this as end of special output
		print '*' x 76; print "\n";
		print '*' x 76; print "\n";
	}

	return;
}


sub DisplayHelp
{
	print "System Summaries web server install script\n";
	print "synopsis: install.pl [ -d ] [ -f ] [ -h ]\n";
	print "\t-d\tTurn on debugging output\n";
	print "\t-f\tForce the script to run even though you're not root\n";
	print "\t-h\tPrints what you're reading now\n";

	return;
}


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

STDOUT->autoflush(1);
STDERR->autoflush(1);

ProcessCommandLine();
InitializeStuff();
CheckSyssummAccount();
SetupEmail();
SetupWeb();
print "syssumm install.pl complete...\n";
PrintComments();

unlink ".ran.install";

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