#!/usr/bin/perl -w
##############################################################################
# $Id: config,v 1.4 1999/09/03 23:52:49 jheiss Exp jheiss $
##############################################################################
# This script is a replacement for the add_install_client script which Sun
# provides for configuring JumpStart clients on a boot server.  This script
# does vastly more error checking and supports an enterprise size rollout
# using JumpStart.
#
# Site dependent things:
# - bootservers() needs to return a list of boot servers
# - The various hashes in profiles.pl need to be defined
# - @INSTALL_SERVERS
# - ??
##############################################################################
# Copyright (C) 1999  Jason Heiss (jheiss@ofb.net)
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
##############################################################################
# Pre-RCS version control:
# created 07/10/97
# last modified 05/07/98
##############################################################################
# $Log: config,v $
# Revision 1.4  1999/09/03 23:52:49  jheiss
# Added a comment about the Ethernet address comparison.
# Changed the root password check to use grep ^root instead of head -1
# on the passwd and shadow files, just in case root isn't the first
# entry.
# Fixed the capitalization of JumpStart in a few places.
#
# Revision 1.3  1999/08/26 04:06:57  jheiss
# Added copyright and GNU license message.
#
# Revision 1.2  1999/08/09 03:58:42  jheiss
# Changed path to Perl
# Fully specified all Getopts option variables
# Added default debug level of zero
#
# Revision 1.1  1999/08/09 03:19:14  jheiss
# Initial revision
#
##############################################################################

#########################
# Modules and libraries #
#########################

#use strict;

require 'basedir.pl';
require 'bootservers.pl';
require 'can.pl';
require 'checkrules.pl';
require 'facts.pl';
require 'info.pl';
require 'passwd.pl';
require 'process.pl';
require 'profiles.pl';
require 'status.pl';
require 'subnet.pl';
use Getopt::Std;
#use strict;

#############
# Constants #
#############

# Known install servers.  Note that the first one in the list is the
# first one that is defaulted to when the client isn't on a subnet with
# any of them.  I.e. put your fastest server first.
my @INSTALL_SERVERS = ('hscs025', 'hscs022', 'hscs056', 'hscj002');

my $NIS_MASTER = `ypwhich -m hosts`;

# Maps that we might edit.  Other sites might list hosts here for example.
# We can't edit the hosts map because it is controlled by Tivoli so we
# don't need to try to push it.
my @MAPS_TO_PUSH = ('ethers', 'bootparams');

###################
# Other variables #
###################

my $silent = 0;
my $supersilent = 0;
my $verbose = 0;
my $superverbose = 0;

my (%bootservers_by_subnet, %installservers_by_subnet);

my @servers_restarted;

my $success = $FALSE;

my %rogue_subnets;

#############################
# Read command line options #
#############################

my $rvalue = getopts('hD:HXYZI:V:S:m:n:a:i:e:f:F:b:r:J:');

unless ($rvalue)
{
	print <<EOF;

You gave invalid options, probably by specifying an option which requires
an argument without specifying the argument.
EOF

	usage_message(1);
}

if ($Getopt::Std::opt_h)
{
	usage_message();
}

# General options
my $debug_level = $Getopt::Std::opt_D;
my $harmless = $Getopt::Std::opt_H;

# Image and version
my $image = $Getopt::Std::opt_I;
my $image_version = $Getopt::Std::opt_V;
my $subimage = $Getopt::Std::opt_S;

# -m: For configuring a single machine that is up and on the network
my $machine = $Getopt::Std::opt_m;    # Hostname

# -n: For configuring a new or down (i.e. not up and on the network) machine
my $newmachine = $Getopt::Std::opt_n; # Hostname
my $newarch = $Getopt::Std::opt_a;   # Architecture like sun4m or sun4u
my $newip = $Getopt::Std::opt_i;      # IP address
my $newether = $Getopt::Std::opt_e;   # Ethernet address

# -f: Specify a file which contains a list of hostnames to configure
my $hostname_file = $Getopt::Std::opt_f;
# -F: Same as -f but with a little different behaviour.  When we configure
#     a machine a record of that is stored in the status file.  With -m or
#     -n, even if a machine has already been configured we re-configure it.
#     The assumption is that if the user is specifically asking us to
#     configure some host they probably have a reason for re-configuring it.
#     -f acts the same way except that it takes a list of hosts.  But with
#     -F we skip the hosts that have already been configured.  If the user
#     has a large list of hosts to configure there will probably be at least
#     a few that fail for whatever reason.  The user can fix the
#     problems with those few and re-run config with -F and only have to
#     wait for those few to be checked.
my $hostname_file_norecheck = $Getopt::Std::opt_F;

# -b: Specify which building the machine is in.  We store this in the status
#     file which then makes this machine show up in the right place on the
#     status web pages.
my $building = $Getopt::Std::opt_b;
# -r: Specify which room the machine is in.
my $room = $Getopt::Std::opt_r;

# Specify a specific install server.  Useful for testing and stuff.
my $specific_install_server = $Getopt::Std::opt_J;

###############################
# Available image information #
###############################
if ($Getopt::Std::opt_X)
{
	print "The available images are:\n";
	print_all_images();
	exit;
}
elsif ($Getopt::Std::opt_Y)
{
	unless (defined($image))
	{
		print "\nPlease specify (with -I) the image for which you would " .
			"like the\navailable versions displayed\n";
		usage_message(1);
	}

	print "The available versions of the '$image' image are:\n";
	print_all_versions($image);
	exit;
}
elsif ($Getopt::Std::opt_Z)
{
	unless (defined($image) && defined($image_version))
	{
		print "\nPlease specify the image (-I) and version (-V) for which " .
			"you would like\nthe available subimages displayed\n";
		usage_message(1);
	}

	print "The available subimages for version '$image_version' of the " .
		"'$image' image are:\n";
	print_all_subimages($image, $image_version);
	exit;
}

#######################
# Debugging variables #
#######################
$debug_level = 0 if (! defined $debug_level);
DSWITCH:
{
	if ($debug_level == -2) { $supersilent = $TRUE; last DSWITCH; }
	if ($debug_level == -1) { $silent = $TRUE; last DSWITCH; }
	if ($debug_level == 1) { $verbose = $TRUE; last DSWITCH; }
	if ($debug_level == 2) { $superverbose = $TRUE; last DSWITCH; }
}

# Supersilent implies silent
$silent = 1 if ($supersilent);
# Superverbose implies verbose
$verbose = 1 if ($superverbose);

##########################
# Check image paramaters #
##########################

# Image and image version are required
unless (defined($image) && defined($image_version))
{
	print "\nYou must specify an image and image version\n";
	usage_message(1);
}

# Subimage is optional
$subimage = 'default' unless (defined($subimage));

# Now check that they gave us valid $image, $image_version and $subimage
my $profcheck = check_profile($image, $image_version, $subimage);
if ($profcheck == $BADIMAGE)
{
	print "\n$image is not a defined image\n";
	usage_message(1);
}
if ($profcheck == $BADVERSION)
{
	print "\nInvalid image version for $image image\n";
	usage_message(1);
}
if ($profcheck == $BADSUBIMAGE)
{
	print "\nInvalid subimage for $image image version $image_version\n";
	usage_message(1);
}

#####################################################
# Check that they specified some hosts to configure #
#####################################################

unless(defined($machine) ||
		defined($newmachine) ||
		defined($hostname_file) ||
		defined($hostname_file_norecheck))
{
	print "\nYou must specify one or more hosts to configure\n";
	usage_message(1);
}

if (defined($newmachine) && (!$newarch || !$newip || !$newether))
{
	print "\nFor a new machine you must specify the arch and IP and ethernet addresses\n";
	usage_message(1);
}

#######################################
# Make sure we're being run correctly #
#######################################

unless ($< == 0 && `hostname` =~ /^$NIS_MASTER/)
{
	print <<EOF;

You need to be root on the NIS master ($NIS_MASTER) for this script to work.
EOF

	usage_message(1);
}

##################################################
# Make sure we're working in the right directory #
##################################################
chdir($BASEDIR);

#########################
# Get the root password #
#########################
if (!$newmachine)
{
	$rootpass = getpass("current root password for the host(s)");
}

################################
# Calculate date for later use #
################################
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
	localtime(time);
$mon++;  # Switch month to a 1-12 range
$year = $year + 1900;  # Get our full year back  (Yes, this is Y2K compliant)
# Pad with a zero if appropriate
$mon = "0$mon" if ($mon <= 9);
$mday = "0$mday" if ($mday <= 9);
my $date = "$mon$mday$year";

##############################
# Build list of boot servers #
##############################
stat_print("Please wait while we build up a list of available boot servers\n");
my @bootservers = bootservers();

####################################
# Build list of hosts to configure #
####################################
my @hosts_to_config;
if ($machine)
{
	push(@hosts_to_config, $machine);
}
elsif ($newmachine)
{
	push(@hosts_to_config, $newmachine);
}
elsif ($hostname_file)
{
	#open(HOSTFILE, $hostname_file) || die "Failed to open hostname input file";
	@hosts_to_config = readinputfile($hostname_file);
}
elsif ($hostname_file_norecheck)
{
	#open(HOSTFILE, $hostname_file_norecheck) ||
		#die "Failed to open hostname input file";
	@hosts_to_config = readinputfile($hostname_file_norecheck);
}
else
{
	die "What happened??";
}

if (scalar(@hosts_to_config) == 0)
{
	print "\nYou didn't specify any hosts to configure\n";
	usage_message(1);
}

##############################
# Initial status sytem stuff #
##############################

# Get lock on status system
verbose_print("Getting lock on status system\n");
superlock();

# Handle the user hitting ^C gracefully.  We don't initiate this until here
# because it cleans up our lock files and we want to make sure that it is
# our own lock files that we are cleaning.
$SIG{INT} = 'quit';

# Read in the status of everyone
verbose_print("Reading status file\n");
readstat();

########################################################################
# Store the building and room in the appropriate variables so they get #
# written out the to status file when it is saved.                     #
########################################################################

if (defined($machine))
{
	$building{$machine} = $building;
	$room{$machine} = $room;
}
elsif (defined($newmachine))
{
	$building{$newmachine} = $building;
	$room{$newmachine} = $room;
}

###################
# The mess begins #
###################

# Request unbuffered output, makes things look nicer
$| = 1;

silent_print("********************************************************************************\n");

HOST: foreach $host (@hosts_to_config)
{
	# We skip to the next host if this is a host we've successfully
	# configured already and we've been told not to recheck those hosts.
	# Otherwise we reset everything to default values.
	if (inarray($host, @hosts) &&
		$status{$host}[0] == $CONFIGSUCCESS &&
		defined($hostname_file_norecheck))
	{
		next HOST;
	}
	else
	{
		unless (inarray($host, @hosts))
		{
			push(@hosts, $host);
		}

		if (defined($newmachine) && $host eq $newmachine)
		{
			$arch{$host} = $newarch;
			$ether{$host} = $newether;
			$ip{$host} = $newip;
			$down{$host} = 1;
		}
		else
		{
			$arch{$host} = 'unknown';
			$ether{$host} = 'unknown';
			$ip{$host} = 'unknown';
			$down{$host} = 0;
		}
		$building{$host} = 'unknown';
		$room{$host} = 'unknown';
		setstat($newmachine, $NOTTOUCHED);
	}

	# This is inside the foreach loop so that we can stop and restart this
	# script for whatever reason and not lose all of the changes we've
	# made.  It is here instead of at the bottom (where it would
	# preferably be) because I have a number of 'next' calls in situations
	# where there are serious errors with a particular machine.
	verbose_print("Updating status file\n");
	writestat();

	stat_print("********************************************************************************\n");
	stat_print("Configuring JumpStart for $host\n");
	silent_print("Configuring $host: ");

	###########################################################
	# Make sure the thing is alive and well (if it should be) #
	###########################################################

	if (!$down{$host})
	{
		stat_print("Attempting to contact machine: ");
		if (!can_ping($host))
		{
			handle_error($host, $CONFIGNOPING);
			next HOST;
		}
		if (!can_rsh($host))
		{
			handle_error($host, $CONFIGNORSH);
			next HOST;
		}
		stat_print("Ok\n");
	}

	##################
	# Info gathering #
	##################

	if (!$down{$host})
	{
		stat_print("Collecting information from host\n");

		stat_print("  Getting architecture: ");
		$arch = get_architecture($host);
		if (known_arch($image, $image_version, $subimage, $arch))
		{
			$arch{$host} = $arch;
			stat_print("$arch\n");
		}
		else
		{
			handle_error($host, $UNSUPPARCH);
			next HOST;
		}

		stat_print("  Getting OS version: ");
		$osver = get_os_version($host);
		stat_print("$osver\n");

		stat_print("  Getting ethernet address: ");
		$ether{$host} = get_mac_address($host);
		stat_print("$ether{$host}\n");

		stat_print("  Getting IP address: ");
		$ip{$host} = get_ip_address($host);
		stat_print("$ip{$host}\n");

		stat_print("  Getting NIS server: ");
		$nis_server = get_nis_server($host);
		# Rob had one fail because the NIS server came back as
		# hsca038.ES.HAC.COM
		($nis_server) = split(/\./, $nis_server);
		stat_print("$nis_server\n");
	}
	else
	{
		stat_print("Machine is new or down, our information says:\n");

		if (!$arch{$host} || !$ether{$host} || !$ip{$host})
		{
			handle_error($host, $INSUFFINFO);
			next HOST;
		}

		stat_print("  Architecture: ");
		if (known_arch($image, $image_version, $subimage, $arch{$host}))
		{
			stat_print("$arch{$host}\n");
		}
		else
		{
			handle_error($host, $UNSUPPARCH);
			next HOST;
		}

		stat_print("  Ethernet address: $ether{$host}\n");
		stat_print("  IP address: $ip{$host}\n");
	}

	##########
	# ethers #
	##########

	stat_print("Checking NIS maps for consistency and correctness\n");

	stat_print("  Checking ethers: ");

	my $bad_ether_entry = 0;
	my $good_ether_entry = 0;

	open(ETHERS, "/var/yp/src/ethers");
	open(NEW_ETHERS, ">/var/yp/src/ethers.new");
	while(<ETHERS>)
	{
		my ($ether, $etherhost) = split;

		# The ethernet address comparisons here should do a numeric
		# comparison instead of a string comparison.  Currently it
		# thinks 8:0:20 and 08:00:20 are different addresses.

		if ($ether eq $ether{$host} && $etherhost ne $host)
		{
			verbose_print("Wrong host ");
			$bad_ether_entry = 1;
		}
		elsif ($etherhost eq $host && $ether ne $ether{$host})
		{
			verbose_print("Wrong enet ");
			$bad_ether_entry = 1;
		}
		elsif ($etherhost eq $host && $ether eq $ether{$host})
		{
			verbose_print("Good entry ");
			print NEW_ETHERS $_ unless ($good_ether_entry);
			$good_ether_entry = 1;
		}
		else
		{
			print NEW_ETHERS $_;
		}
	}

	unless ($good_ether_entry)
	{
		print NEW_ETHERS "$ether{$host}\t\t$host\n";
	}
	close(ETHERS);
	close(NEW_ETHERS);
	unless ($harmless)
	{
		system("cp /var/yp/src/ethers /var/yp/src/ethers.$date");
		system("cp /var/yp/src/ethers.new /var/yp/src/ethers");
	}
	unlink("/var/yp/src/ether.new");
	#print "bad_ether_entry: $bad_ether_entry\n";
	#print "good_ether_entry: $good_ether_entry\n";
	if ($bad_ether_entry)
	{
		stat_print("Fixed\n");
	}
	elsif ($good_ether_entry)
	{
		stat_print("Ok\n");
	}
	else
	{
		stat_print("Added\n");
	}

	#########
	# hosts #
	#########

	stat_print("  Checking hosts: ");

	my $bad_host_entry = 0;
	my $good_host_entry = 0;
	my $host_error = 0;

	open(HOSTS, "/var/yp/src/hosts");
	HOSTLINE: while(<HOSTS>)
	{
		# Skip comments and blank lines
		next HOSTLINE if (/^#/);
		next HOSTLINE if (/^\s*\n$/);

		my ($ip, $iphost) = split;

		if ($ip eq $ip{$host} && $iphost ne $host)
		{
			handle_error($host, $IPWRONGHOST);
			$bad_host_entry = 1;
		}
		elsif ($iphost eq $host && $ip ne $ip{$host})
		{
			handle_error($host, $HOSTWRONGIP);
			$bad_host_entry = 1;
		}
		elsif ($iphost eq $host && $ip eq $ip{$host})
		{
			verbose_print("Good entry ");
			$good_host_entry = 1;
		}
	}
	close(HOSTS);

	#print "bad_host_entry: $bad_host_entry\n";
	#print "good_host_entry: $good_host_entry\n";

	if ($bad_host_entry)
	{
		$host_error = 1;
	}

	unless ($good_host_entry || $bad_host_entry)
	{
		handle_error($host, $HOSTSNOENTRY);
		$host_error = 1;
	}

	stat_print("Ok\n") unless ($host_error);

	#######################################################################
	# Check to make sure that the machine is bound to an approved NIS     #
	# server.  Otherwise we might have problems when we do the JumpStart. #
	#######################################################################

	unless ($down{$host})
	{
		stat_print("Checking NIS server: ");

		my $nis_server_error = 0;
		my $approved = 0;

		# The HSC naming convention for machines with multiple network
		# interfaces is to name them something like foo-0, foo-1, etc.
		# So we want to check the NIS server the machine is using
		# against anything like that.

		my ($server_base) = split(/\-/, $nis_server);

		open(YPSERVERS, "/var/yp/src/ypservers");
		YPSERV: while(<YPSERVERS>)
		{
			chop;
			my ($test_server_base) = split(/\-/);

			if ($test_server_base eq $server_base)
			{
				$approved = 1;
				last YPSERV;
			}
		}
		close(YPSERVERS);

		if ($approved == 0)
		{
			handle_error($host, $BADNISSERV);
		}
		else
		{
			stat_print("Ok\n");
		}
	}

	#################################################
	# Root password and /usr/local/do_not_jumpstart #
	#################################################

	if (!$down{$host})
	{
		stat_print("Checking root password: ");
		if ($osver =~ /^4/)
		{
			chop($cryptpass =
					rsh_noerr($host, 'grep ^root /etc/passwd | cut -d : -f 2'));
		}
		else
		{
			chop($cryptpass =
					rsh_noerr($host, 'grep ^root /etc/shadow | cut -d : -f 2'));
		}
		$salt = substr($cryptpass, 0, 2);
		if (crypt($rootpass, $salt) ne $cryptpass)
		{
			verbose_print("Root password on $host is not the production " .
				"root password\n");
			handle_error($host, $CONFIGDIFFRP);
		}
		else
		{
			stat_print("Ok\n");
		}

		stat_print("Checking for do_not_jumpstart file: ");
		if (rsh_file_exists($host, '/usr/local/do_not_jumpstart'))
		{
			verbose_print("$host says it doesn't want to be JumpStarted\n");
			handle_error($host, $CONFIGDNJ);
		}
		else
		{
			stat_print("Ok\n");
		}
	}

	#################################
	# Pick boot and install servers #
	#################################

	# We don't want to try to do this if the host map checks fail because
	# pick_by_subnet() needs to know the machine's IP number in order
	# to figure out which subnet it is on.
	#
	# We also cache these choices to speed things up.

	my $install_server = '';
	if (!$host_error)
	{
		stat_print("Picking a boot server: ");
		if (defined($bootservers_by_subnet{subnet($host)}))
		{
			verbose_print("\n  Using cached entry\n");
			$bootserver = $bootservers_by_subnet{subnet($host)};
		}
		else
		{
			verbose_print("\n  Picking one via pick_by_subnet\n");
			$bootserver = pick_by_subnet($host, @bootservers);
		}
		if ($bootserver eq '')
		{
			handle_error($host, $NOBOOTSERV);
		}
		else
		{
			verbose_print("  Caching bootserver for later use\n");
			$bootservers_by_subnet{subnet($host)} = $bootserver;
			stat_print("$bootserver\n");
		}

		# For the install server we need to find one that has the
		# right images on it.  We first try to find one on the same
		# subnet.  If that fails then we iterate through the others.
		stat_print("Picking an install server: ");
		if ($specific_install_server)
		{
			# check_install_server returns 0 if everything is ok
			unless (check_install_server($specific_install_server,
										$image,
										$image_version,
										$subimage,
										$arch{$host}))
			{
				verbose_print("\n  Using specified install server\n");
				$install_server = $specific_install_server;
			}
			else
			{
				verbose_print("\n  Errors with specified install server\n");
				# Probably not quite right, but ok for now...
				handle_error($host, $NOINSTALLSERV);
			}
		}
		elsif (defined($installservers_by_subnet{subnet($host)}))
		{
			verbose_print("\n  Using cached entry\n");
			$install_server = $installservers_by_subnet{subnet($host)};
		}
		else
		{
			verbose_print("\n  Picking one for this subnet\n");
			$is_subnet = pick_by_subnet($host, @INSTALL_SERVERS);
			verbose_print("  pick_by_subnet returned '$is_subnet'\n");
			unless ($is_subnet eq '')
			{
				verbose_print("  Checking install server ($is_subnet) on " .
					"same subnet\n");
				# check_install_server returns various negative numbers if
				# there is something wrong with the install server, 0 if it
				# is properly set up for this image, version, etc.
				$cis_return = check_install_server($is_subnet,
													$image,
													$image_version,
													$subimage,
													$arch{$host});

				if ($cis_return == 0)
				{
					verbose_print("  Keeping $is_subnet\n");
					$install_server = $is_subnet;
				}
				else
				{
					verbose_print("  Nope, problems with $is_subnet\n");
				}
			}
			if ($install_server eq '')
			{
				# Didn't find one on the same subnet, try all of the others
				IS: foreach $is (@INSTALL_SERVERS)
				{
					verbose_print("  Trying install server $is\n");
					$cis_return = check_install_server($is,
														$image,
														$image_version,
														$subimage,
														$arch{$host});

					if ($cis_return == 0)
					{
						verbose_print("    Install server is good\n");
						$install_server = $is;
						last IS;
					}
					else
					{
						verbose_print("    Nope, problems with $is\n");
					}
				}
			}
		}

		if ($install_server eq '')
		{
			handle_error($host, $NOINSTALLSERV);
		}
		else
		{
			verbose_print("  Caching install server for later use\n");
			$installservers_by_subnet{subnet($host)} = $install_server;
			stat_print("$install_server\n");
		}
	}

	###############
	# Check rules #
	###############

	# We can't check this on new or down machines so we'll just have to
	# assume those are correct...  :)
	if (!$down{$host})
	{
		stat_print("Checking rules.ok: ");

		if (checkrules($host,
						$arch{$host},
						$osver,
						profile_lookup(\%PROFILE,
							$image,
							$image_version,
							$subimage,
							$arch{$host})))
		{
			stat_print("Ok\n");
		}
		else
		{
			handle_error($host, $CONFIGRULESFAIL);
		}
	}

	################################################################
	# Check for rogue RARP and bootparams servers                  #
	#                                                              #
	# We search for rogue bootparams servers using rpcinfo.        #
	# Because rpcinfo takes about 12 seconds to run, we only check #
	# a given subnet once.  Any other hosts on that subnet are     #
	# given the same status.  It would be nicer if we could just   #
	# check for bootparams servers which respond to requests for   #
	# this hostname.  rpcinfo just checks if the machine is        #
	# running a bootparams server period.  It is quite possible    #
	# that there are bootparams servers out there that don't have  #
	# info about this machine.                                     #
	#                                                              #
	# We don't have a way to check for RARP servers at this point. #
	################################################################

	if ($bootserver)
	{
		stat_print("Checking for rogue bootparams servers: ");
		# If the subnet has already failed just assign the proper error
		# code to the host.
		my $rogue_found;
		if ($rogue_subnet{subnet($host)} == $TRUE)
		{
			print "\nSubnet " . subnet($host) . " is already marked as bad\n"
				if ($superverbose);
			$rogue_found = 1;
			handle_error($host, $ROGUEBOOTPARAMS);
		}
		elsif (defined($rogue_subnet{subnet($host)}) &&
				$rogue_subnet{subnet($host)} == $FALSE)
		{
			print "\nSubnet " . subnet($host) . " is clear\n"
				if ($superverbose);
		}
		elsif (! defined($rogue_subnet{subnet($host)}))
		{
			# Run rpcinfo on the boot server
			my $rpcinfo_output = rsh($bootserver,
								"/usr/bin/rpcinfo -b 100026 1 | sort | uniq");
			my @rpcinfo_lines = split(/\n/, $rpcinfo_output);
			print "\nrpcinfo gave us:\n$rpcinfo_output\n" if ($superverbose);

			# Figure out the IP address of the interface on the boot server
			# that is on the same subnet as the host being configured.
			my $bshostnames = rsh($bootserver, "cat /etc/hostname.*[0-9]");
			print "cat of hostnames returned:\n$bshostnames" if ($superverbose);
			my $bsip;
			foreach $bshost (split(/\n/, $bshostnames))
			{
				if (same_subnet($bshost, $host))
				{
					($bsip) = split(' ', `ypmatch $bshost hosts`);
				}
			}
			print "Looks the the relevant IP address for the boot server " .
				"is $bsip\n" if ($superverbose);

			# A sample line of output from the rpcinfo command we run above
			# look like:
			#
			# 147.17.203.168.144.177  hscns01-qe2
			#
			# The first four octets are the IP address.  I'm not sure what
			# the last two are.
			#
			# It would be a fairly simple task to figure out if there were
			# rogue servers if all of our boot servers had only one
			# network interface.  However, all of the network servers have
			# at least one quad card in them.  So we get responses from
			# at least every interface in the network server as well as
			# any rogue servers on any of those subnets.  So we first
			# isolate the responses down to the subnet of interest and then
			# look for rogues.

			my $line;
			my @rogues;
			my $rogue_killed;
			foreach $line (@rpcinfo_lines)
			{
				my ($ipplus, $rpcinfo_host) = split(' ', $line);
				my @ipplus_parts = split(/\./, $ipplus);
				my $rpcinfo_ip = "$ipplus_parts[0].$ipplus_parts[1]." .
					"$ipplus_parts[2].$ipplus_parts[3]";

				# We pass the IP address from the rpcinfo output to same_subnet
				# because I don't want to trust that every rogue has a
				# registred hostname.
				if (same_subnet($host, $rpcinfo_ip) && $rpcinfo_ip ne $bsip)
				{
					print "IP $rpcinfo_ip is a rogue\n" if ($superverbose);
					if (can_ping($rpcinfo_ip) &&
						can_rsh($rpcinfo_ip) &&
						rsh($rpcinfo_ip, 'uname -s') eq "SunOS\n")
					{
						# Kill the bootparams server
						process_kill($rpcinfo_ip, 'rpc.bootparamd')
							unless ($harmless);

						# Kill the rarp server
						# We'll move this once we have real rogue rarp
						# server checking.
						process_kill($rpcinfo_ip, 'in.rarpd')
							unless ($harmless);

						# Sometimes just killing bootparamd isn't enough
						# to get the service unregistered with the portmapper
						# which is what we're really checking.  So we force
						# the portmapper to unregister the service as well.
						rsh($rpcinfo_ip, 'rpcinfo -d 100026 1')
							unless ($harmless);

						# The existence of /tftpboot is what triggers SunOS
						# and Solaris to start the rarp and bootparams
						# servers.  As such, we move that directory to
						# /tftpboot.old so the servers won't get started
						# if the machine is rebooted.
						rsh($rpcinfo_ip, 'mv /tftpboot /tftpboot.old')
							unless ($harmless);

						print "Rogue $rpcinfo_ip killed\n" if ($superverbose);
						$rogue_killed = 1;
					}
					else
					{
						push(@rogues, "$rpcinfo_host ($rpcinfo_ip)");
					}
				}
			}

			if (scalar(@rogues) != 0)
			{
				print "\nThe following  ". scalar(@rogues) .
						" machines are rogue bootparam servers:\n"
					if ($superverbose);
				foreach $rogue (@rogues)
				{
					print "$rogue\n";
				}

				$rogue_found = 1;
				handle_error($host, $ROGUEBOOTPARAMS);
				$rogue_subnets{subnet($host)} = $TRUE;
			}
			else
			{
				$rogue_subnets{subnet($host)} = $FALSE;
			}
		}

		unless ($rogue_found)
		{
			if ($rogue_killed)
			{
				stat_print("Rogue killed\n");
			}
			else
			{
				stat_print("Ok\n");
			}
		}
	}

	################################################################
	# Bail if there's been an error since the rest of this assumes #
	# things are ok with this machine.                             #
	################################################################

	if ($error_log{$host})
	{
		stat_print("Error(s) encountered, please fix things and try again\n");
		silent_print("Error\n");
		next HOST;
	}
	else
	{
		# Set a flag indicating that we successfully configured at least
		# one machine.  We use this to determine if we should push the
		# NIS maps.
		$success = $TRUE;
	}

	#################################################
	# Setup the various files/maps for this machine #
	#################################################

	######################
	# bootparams NIS map #
	######################
	my $root_entry = "root=$install_server:" .
		profile_lookup(\%BOOT_IMAGE,
						$image,
						$image_version,
						$subimage,
						$arch{$host});
	my $cdimage_entry = "install=$install_server:" .
		profile_lookup(\%CD_IMAGE,
						$image,
						$image_version,
						$subimage,
						$arch{$host});
	my $profile_entry = "install_config=$install_server:" .
		profile_lookup(\%PROFILE,
						$image,
						$image_version,
						$subimage,
						$arch{$host});

	$bpentry = "$host $root_entry $cdimage_entry $profile_entry boottype=:in";

	verbose_print("The following entry will be added to the bootparams NIS " .
		"map:\n\n$bpentry\n\n");
	stat_print("Updating bootparams NIS map: ");

	open(BOOTPARAMS, "/var/yp/src/bootparams");
	open(BOOTPARAMS_NEW, ">/var/yp/src/bootparams.new");
	while(<BOOTPARAMS>)
	{
		my ($boothost) = split;

		unless ($boothost eq $host)
		{
			print BOOTPARAMS_NEW $_;
		}
	}
	print BOOTPARAMS_NEW "$bpentry\n";
	close(BOOTPARAMS);
	close(BOOTPARAMS_NEW);
	unless ($harmless)
	{
		system("cp /var/yp/src/bootparams /var/yp/src/bootparams.$date");
		system("cp /var/yp/src/bootparams.new /var/yp/src/bootparams");
	}
	unlink("/var/yp/src/bootparams.new");
	stat_print("Ok\n");

	#############
	# /tftpboot #
	#############
	stat_print("Removing any existing links in /tftpboot on boot server: ");
	unless ($harmless)
	{
		rsh($bootserver, "rm -f /tftpboot/" . hexify($ip{$host}));
		rsh($bootserver, "rm -f /tftpboot/" . hexify($ip{$host}) . ".SUN4?");
	}
	stat_print("Ok\n");

	stat_print("Making sure /tftpboot exists on boot server: ");
	unless ($harmless)
	{
		rsh($bootserver, "mkdir /tftpboot");
	}
	stat_print("Ok\n");

	stat_print("Checking for proper inetboot file in /tftpboot " .
		"on boot server: ");
	if (rsh_file_exists($bootserver,
						"/tftpboot/" .
							profile_lookup(\%TFTPFILE,
											$image,
											$image_version,
											$subimage,
											$arch{$host})))
	{
		stat_print("Ok\n");
	}
	else
	{
		unless ($harmless)
		{
			rcp($bootserver,
				profile_lookup(\%INETBOOT,
								$image,
								$image_version,
								$subimage,
								$arch{$host}),
				"/tftpboot/" .
					profile_lookup(\%TFTPFILE,
									$image,
									$image_version,
									$subimage,
									$arch{$host}));
		}
		stat_print("installed\n");
	}

	stat_print("Adding link(s) to /tftpboot on the boot server: ");
	unless ($harmless)
	{
		rsh($bootserver,
			"ln -s " .
				profile_lookup(\%TFTPFILE,
								$image,
								$image_version,
								$subimage,
								$arch{$host}) .
				" /tftpboot/" . hexify($ip{$host}));
		my $arch_upper = $arch{$host};
		$arch_upper =~ tr/[a-z]/[A-Z]/;
		rsh($bootserver,
			"ln -s " .
				profile_lookup(\%TFTPFILE,
								$image,
								$image_version,
								$subimage,
								$arch{$host}) .
				" /tftpboot/" . hexify($ip{$host}) . ".$arch_upper");
	}
	stat_print("Ok\n");

	################################
	# Check/fix /etc/nsswitch.conf #
	################################
	stat_print("Checking bootparams and ethers entries in nsswitch.conf " .
		"on $bootserver: ");

	# The lines we'd like to have in nsswitch.conf for bootparams and ethers
	my $bootline = "bootparams: nis [NOTFOUND=return] files\n";
	my $etherline = "ethers:     nis [NOTFOUND=return] files\n";
	# The square brackets are metacharacters and mess things up
	# when we try to match the lines.  So we run them through quotemeta
	# to escape that stuff.
	my $q_bootline = quotemeta($bootline);
	my $q_etherline = quotemeta($etherline);

	my $nsswitch = rsh($bootserver, "cat /etc/nsswitch.conf");
	unless ($nsswitch =~ /^$q_bootline/m && $nsswitch =~ /$q_etherline/m)
	{
		open(NSSWITCH, "rsh $bootserver cat /etc/nsswitch.conf |");
		open(NEW_NSS, ">/tmp/new_nsswitch.conf");
		my $bootfound = 0;
		my $etherfound = 0;
		while(<NSSWITCH>)
		{
			if (/^bootparams:/)
			{
				print NEW_NSS $bootline;
				$bootfound = 1;
			}
			elsif (/^ethers:/)
			{
				print NEW_NSS $etherline;
				$etherfound = 1;
			}
			else
			{
				print NEW_NSS $_;
			}
		}
		close(NSSWITCH);
		print NEW_NSS $bootline unless ($bootfound);
		print NEW_NSS $etherline unless ($etherfound);
		close(NEW_NSS);
		unless ($harmless)
		{
			rsh($bootserver, 'cp /etc/nsswitch.conf /etc/nsswitch.conf-');
			rcp($bootserver, '/tmp/new_nsswitch.conf', '/etc/nsswitch.conf');
		}
		unlink("/tmp/new_nsswitch.conf");

		stat_print("Fixed\n");
	}
	else
	{
		stat_print("Ok\n");
	}

	#############################
	# Check/fix /etc/inetd.conf #
	#############################
	stat_print("Checking tftpd entry in inetd.conf on $bootserver: ");
	unless (rsh($bootserver, "cat /etc/inetd.conf") =~ /^tftp/m)
	{
		open(INETD, "rsh $bootserver cat /etc/inetd.conf |");
		open(NEW_INETD, ">/tmp/new_inetd.conf");
		my $tftpfound = 0;
		while(<INETD>)
		{
			if (/^tftp/)  # Shouldn't happen, but just in case...
			{
				print NEW_INETD $_;
				$tftpfound = 1;
			}
			elsif (/^#tftp/)
			{
				# Uncomment it
				print NEW_INETD substr($_, 1);
				$tftpfound = 1;
			}
			else
			{
				print NEW_INETD $_;
			}
		}
		close(INETD);
		if (!$tftpfound)
		{
			print NEW_INETD "tftp\tdgram\tudp\twait\troot\t/usr/sbin/in.tftpd\tin.tftpd -s /tftpboot\n";
		}
		close(NEW_INETD);
		unless ($harmless)
		{
			rsh($bootserver, 'cp /etc/inetd.conf /etc/inetd.conf-');
			rcp($bootserver, '/tmp/new_inetd.conf', '/etc/inetd.conf');
			process_killhup($bootserver, 'inetd');
		}
		unlink("/tmp/new_inetd.conf");

		stat_print("Fixed\n");
	}
	else
	{
		stat_print("Ok\n");
	}

	#########################################
	# Kill and restart rarpd and bootparamd #
	#########################################
	# We only need to restart the servers once, so in the interest of
	# time we keep track of which boot servers we've already restarted
	# the servers on.
	unless (inarray($bootserver, @servers_restarted))
	{
		push(@servers_restarted, $bootserver);
	
		stat_print("Killing and restarting rarpd and bootparamd on " .
			"$bootserver\n");
		stat_print("  Killing in.rarpd\n");
		process_kill($bootserver, 'in.rarpd') unless ($harmless);
		stat_print("  Killing rpc.bootparamd\n");
		process_kill($bootserver, 'rpc.bootparamd') unless ($harmless);
		stat_print("  Starting in.rarpd\n");
		rsh($bootserver, '/usr/sbin/in.rarpd -a') unless ($harmless);
		stat_print("  Starting rpc.bootparamd\n");
		rsh($bootserver, '/usr/sbin/rpc.bootparamd') unless ($harmless);
	}

	# Ok, everything is set up for this host.  Set its status to indicate
	# that it was successfully configured.
	setstat($host, $CONFIGSUCCESS);

} # end of foreach loop

stat_print("********************************************************************************\n");

#################
# Push NIS maps #
#################
if ($success)
{
	print <<EOF;

We would normally push the NIS maps here.  However, if you are configuring
several hosts with seperate calls to $0 you might want to wait
until the last host to push the maps because it takes a while.  If that is
the case you have 20 seconds to enter the letter s (for skip) followed by a
return.  You can just hit return to stop waiting and make the maps.
EOF

	my $input = '';
	eval
	{
		local $SIG{ALRM} = sub { die };
		alarm 20;
		$input = <STDIN>;
		alarm 0;
	};

	unless ($input eq "s\n")
	{
		stat_print("Pushing NIS maps:\n");
		foreach $map (@MAPS_TO_PUSH)
		{
			system("cd /var/yp ; /usr/ccs/bin/make $map");
		}
	}
}

################
# Final report #
################

stat_print("********************************************************************************\n");
$first = 1;
SHOST: foreach $shost (@hosts_to_config)
{
	if (!$error_log{$shost})
	{
		if ($first)
		{
			stat_print("********************************************************************************\n");
			stat_print("JumpStart was successfully configured for the following hosts:\n");
			$first = 0;
		}
		print("$shost\n");
	}
}

$first = 1;
EHOST: foreach $ehost (@hosts_to_config)
{
	if ($error_log{$ehost})
	{
		if ($first)
		{
			stat_print("********************************************************************************\n");
			stat_print("There were ERRORS with the following hosts:\n");
			$first = 0;
		}
		stat_print("$ehost:\n");
		stat_print($error_log{$ehost});
	}
}

stat_print("********************************************************************************\n");
stat_print("********************************************************************************\n");

verbose_print("Updating status file\n");
writestat();

verbose_print("Giving back lock on status system\n");
unsuperlock();

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

sub quit
{
	unsuperlock();
	print @_;
	exit;
}

sub usage_message
{
	my $short = $_[0];

	print <<EOF;

Usage: $0 [-h] [-D level] [-H] [-X | -Y | -Z] \\
          [-b building] [-r room] [-J server] \\
          -I image -V version [-S subimage] \\
          -m host | -n host -a arch -i ip -e ether | -f file | -F file
EOF

	if (defined($short) && $short)
	{
		print <<EOF;

-h: Describe all options
EOF
	}
	else
	{
		print <<EOF;

-h: Print this message
-D: Debug level, -1 and -2 reduce output below normal, 1 and 2 increase it
-H: Harmless, don't actually make any changes to anything

-b: Specify which building the host is located in.  (Optional)
-r: Specify which room the host is located in.  (Optional)

-J: Specify a specific install server (for testing purposes only)

-X: Lists all available images
-Y: In conjunction with -I lists all versions of a given image
-Z: In conjunction with -I and -V lists all subimages of a given image version

-I: Image, examples are client, netsvr, appserver  (this option is required)
-V: Image version, examples are 1.7, 2.0, 3.0beta  (this option is required)
-S: Subimage, examples from the 2.0 standard client are js, qj or qjd
    If the image doesn't have subimages just omit this option

-m: Machine, used to configure a single machine.  Requires the machine to be
    running and on the net.  If that is not the case use -n.  Requires an
    argument which is the hostname of the machine to configure.

-n: New machine, like -m but for machines that are new, have moved or are
    otherwise not already running and on the net.
    Requires -a, -i and -e as well.
-a: Kernel architecure, i.e. sun4m or sun4u (only needed for new machines)
-i: IP address (only needed for new machines)
-e: Ethernet address (only needed for new machines)

-f: File containing a list of hostnames to configure.  They are iterated over
    as if you'd specified them individually with -m.
-F: Similar to -f but skips hosts that have been previously configured.  The
    idea here is that if you have a list of hosts to configure you run through
    them once with -f.  That forces all of them to be checked.  Then if there
    are errors with a few you can correct those errors and then re-run the
    list with -F.  Then only the ones that weren't already successfully
    configured will be checked.
EOF
	}

	exit(0);
}

sub known_arch
{
	my $image = $_[0];
	my $version = $_[1];
	my $subimage = $_[2];
	my $arch = $_[3];

	# Make sure they gave us the right arguments
	unless (defined($image) &&
		defined($version) &&
		defined($subimage) &&
		defined($arch))
	{
		die "Invalid arguments to \&known_arch";
	}

	# Figure out if there is an ARCHS array defined for this subimage and if
	# not use the default one for this image and version.
	if (defined($ARCHS{$image}->{$version}->{$subimage}))
	{
		$ra_archs = $ARCHS{$image}->{$version}->{$subimage};
	}
	else
	{
		$ra_archs = $ARCHS{$image}->{$version}->{default};
	}

	return inarray($arch, @{$ra_archs});
}

sub hexify
{
	# Take an IP number in "dotted-quad" format and return it in a
	# hex string format.
	my @ip_parts = split(/\./, $_[0]);

	# Convert to hex, padding with zeros if necessary
	my $hex = sprintf("%2x%2x%2x%2x", @ip_parts);
	$hex =~ s/ /0/g;

	#print "Hexify converted $_[0] to $hex\n";

	$hex =~ tr/a-z/A-Z/;
	return $hex;
}

sub stat_print
{
	unless ($silent)
	{
		print @_;
	}
}

sub silent_print
{
	if ($silent && !$supersilent)
	{
		print @_;
	}
}

sub verbose_print
{
	if ($verbose)
	{
		print @_;
	}
}

sub handle_error
{
	# The status system allows more than one error code to be associated
	# with each host.  We use that here so that someone can run this
	# script unattended and then be able to go back and know all of the
	# errors associated with a machine.  Otherwise they'd only see the
	# last error, fix it and have to re-run this script again to get the
	# next one, etc.  Since running this script can take an exceptionally
	# long time if there are a bunch of un-JumpStartable machines in the
	# input file, that would be really tiresome.

	my $host = $_[0];
	my $error_num = $_[1];

	$error_msg = "    $codestring{$error_num}\n";

	unless (defined($error_log{$host}))
	{
		$error_log{$host} = "";
	}

	$error_log{$host} .= $error_msg;
	
	stat_print("\n" . $error_msg);

	unless ($harmless)
	{
		unless ($erroralready{$host})
		{
			setstat($host, $error_num);
			$erroralready{$host} = 1;
		}
		else
		{
			addstat($host, $error_num);
		}
	}
}

