#!/usr/bin/perl -w
#  ALTRAN_PROLOG_BEGIN_TAG                                                    
#  This is an automatically generated prolog.                                  
#                                                                              
#  Copyright (C) Altran ACT S.A.S. 2019,2021.  All rights reserved.  
#                                                                              
#  ALTRAN_PROLOG_END_TAG                                                      
#                                                                              
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# 61haes_r720 src/43haes/usr/sbin/cluster/utilities/cl_chpasswdutil.sh 1.6.1.6 
#  
# Licensed Materials - Property of IBM 
#  
# COPYRIGHT International Business Machines Corp. 2004,2015 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
#  @(#)  7d4c34b 43haes/usr/sbin/cluster/utilities/cl_chpasswdutil.sh, 726, 2147A_aha726, Feb 05 2021 09:50 PM
use 5.006;
use strict;
use warnings;
use Getopt::Std;

=head1 NAME

cl_chpasswdutil - Utility to manipulate the AIX password facility to either utilize
		  the base AIX password utility, or replace with a link to the HACMP
	          clpasswd utility.

=head1 SYNOPSIS

cl_chpasswdutil [-r | -l { -o } { -g group } ] [-t] [-c] [-s]

=head1 DESCRIPTION

=head1 COPYRIGHT
    (C) COPYRIGHT Internation Business Machines Corp. 2003 All Rights
    Reserved

=head1 TODO

	* Split out common functionality into separate modules

=head1 METHODS

=cut

sub CONFIGURATION_FILE() 		{ "/usr/es/sbin/cluster/etc/clpasswd/config" };
sub CONFIGURATION_FILE_FIRST()	{ "/usr/es/sbin/cluster/etc/clpasswd/config.init" };

sub PASSWD_BINARY_1()			{ "/usr/bin/passwd" };
sub PASSWD_BINARY_2()			{ "/bin/passwd" };
sub CLPASSWD_BINARY()			{ "/usr/es/sbin/cluster/utilities/clpasswd" };
sub DEBUG_LOG_FILE()			{ "/var/hacmp/log/clpasswd.log" };
sub BACKUP_PASSWD_FILE_LOCATION_1()	{ "/usr/es/sbin/cluster/etc/clpasswd/usr_bin_passwd.orig" };
sub BACKUP_PASSWD_FILE_LOCATION_2()	{ "/usr/es/sbin/cluster/etc/clpasswd/bin_passwd.orig" };
sub CONFIG_FILE_KEYS()			{ ("filename", "checksum_partA", "checksum_partB", "version", "timestamp", "backup", "isLinked") };
sub PASSWD_LINKED_STRING()		{ "Linked"   };
sub PASSWD_NOT_LINKED_STRING()	{ "Unlinked" };
sub PASSWD_UNKNOWN_STRING()		{ "Unknown"  };
sub DEBUG_ENABLED()			    { 0 };

sub CMD_GET_LOCALNODE()			{ "/usr/es/sbin/cluster/utilities/get_local_nodename" };
sub CMD_GET_ALL_NODES()			{ "/usr/es/sbin/cluster/utilities/clnodename" };

sub ERR_UNABLE_TO_OPEN_CONFIG_FILE(@)	    { log_return(1, @_); };
sub ERR_UNABLE_TO_CREATE_CONFIG_FILE(@)	    { log_return(2, @_); };
sub ERR_INVALID_CONFIGURATION(@)	        { log_return(3, @_); };
sub ERR_CHECKSUM_FOR_CONFIG_NOT_MATCH(@)    { log_return(4, @_); };
sub ERR_FILE_NOT_FOUND(@)		            { log_return(5, @_); };
sub ERR_UNABLE_TO_CREATE_HA_LINK(@)	        { log_return(6, @_); };
sub ERR_UNABLE_TO_LINK_PASSWD(@)	        { log_return(7, @_); };
sub ERR_FILES_DIFFER(@)			            { log_return(8, @_); };
sub ERR_UNABLE_TO_DISTRIBUTE_CHANGE(@)	    { log_return(9, @_); };

sub SUCCESS(@)				                { log_return(0, @_); };
sub SUCCESS_AND_EXIT(@)			            { log_return(999, @_); };

my $cmd_get_local_node = CMD_GET_LOCALNODE();
my $g_localnodename = `$cmd_get_local_node`;
$g_localnodename =~ s/\n//g;

##
#
# Function:
#       isRootUser
#
# Description:
#       Determines if the current running user is the root user
#
# Arguments:
#       N/A
#
# Returns:
#       0 - if user is NOT the root user
#       1 - if user IS the root user
#
sub isRootUser() {
        my ($real_gid, $real_uid, $eff_gid, $eff_uid) = (-1, -1, -1, -1);
	my $fn = "isRootUser";
        my $cmdoutput = `/usr/bin/whoami`;
        chomp($cmdoutput);

        if ($cmdoutput eq "root") {

                # Get the real and effective gid/uid for this process
                $real_gid = $(;
                $real_uid = $<;
                $eff_gid = $);
                $eff_uid = $>;

                logmsg($fn, __LINE__, "Real      UID = $real_uid GID = $real_gid");
                logmsg($fn, __LINE__, "Effective UID = $eff_uid GID = $eff_gid");

                # The real gid and real uid should match the effective gid/uid for
                # this process.
                if ($real_gid ne $eff_gid || $real_uid ne $eff_uid) { return 0; }

                # If the real gid/uid is set to root, then we're running as root.
                if ($real_gid =~ /\s*0\s*/ && $real_uid == 0) { return 1; }
        }
        return 0;
}

##
# Function:     getCurrentTime
#
# Description:
#               Returns the current time as 04/15/2004 12:15:32
#               or month / day / year  hh:mm:ss
#
# Arguments:
#       none
# 
# Returns:
#       scalar containing current time as string
#
sub getCurrentTime() {
	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
	$mon++; $year += 1900;
	return "$mon/$mday/$year $hour:$min:$sec";
}

##
# Function:     logmsg
#
# Description:
#               Logs messages to a log file with the current time
#               starting on each line in the file and a line number and
#               function name of where the message originated.
#               The log file is for support related functions and is not
#               intended to be viewed by the user, thus no ILS support
#               is available via this function.
#
# Arguments:
#               $: fn - name of the function
#               $: line - line number of script where the message originated
#               $: msg  - message to log
#
# Returns:
#               n/a
#
sub logmsg(@) {
	my ($fn, $line, $msg) = @_;
	my $log_file = DEBUG_LOG_FILE();
	print getCurrentTime() if (DEBUG_ENABLED());
	print ":[ $fn\@$line ]: $msg\n" if (DEBUG_ENABLED());
	open(LOG, ">> $log_file") || return;
	print LOG getCurrentTime();
	print LOG ":[ $fn\@$line ]: $msg\n";
	close(LOG);
}

##
# Function:     log_return
#
# Description:
#       Log the return from a subroutine to a log file to allow
#       support to trace the execution of this script without
#       having to separately recreate the problem and debug
#
# Arguments:
#       $: errorcode - error code value returned from routine
#       $: fn - name of the subroutine / function
#       $: line - line number of where the routine was returned
#       $: reason - message indicating reason for error
#
# Returns:
#       n/a
#
sub log_return(@) {
	my ($errorcode, $fn, $line, $reason) = @_;
	my $log_file = DEBUG_LOG_FILE();
	if (defined $fn && defined $line) {
		if (! defined $reason) { $reason = ""; }
		open(LOG, ">> $log_file") || return $errorcode; # Open the file, or simply return an error code
		print LOG getCurrentTime();
		print LOG ": [ $fn\@$line ]: ERROR [\#$errorcode] $reason\n";
		close(LOG);
	}
	return $errorcode;
}

##
# Function:     dspmsg
#
# Description:
#       Invokes the dspmsg command for the caller with the message_id,
#       message and any arguments in the 124 set from cspoc.cat.
#
# Arguments:
#       $: id - message catalog identifier
#       $: msg - message to log
#       @: args - any arguments to provide to the message
# 
# Returns:
#       n/a
#
sub dspmsg(@) {
    my ($id, $msg, @args) = @_;
    my $arguments = join(" ", @args);
    $msg =~ s/\n/\\n/g;
    my $cmd = "/usr/bin/dspmsg -s 124 cspoc.cat $id \"$msg\" $arguments";
    system($cmd);
}

##
# Function:     check_diff_passwd
#
# Description:
#       Check to see if the checksum stored on disk is the same
#       as the checksum for the specified file.
#
# Arguments:
#       $: file to check checksum against
#       $: original_file - the file we want to lookup the checksum for in the checksum file
#
# Returns:
#       ERR_FILES_DIFFER - if $file is different from the stored checksum
#       SUCCESS - if the checksums are the same
#
sub check_diff_passwd(@) {
	my ($file, $original_file) = @_;
	my $fn = "check_diff_passwd";
	my $config = read_config(CONFIGURATION_FILE_FIRST());
	if (ref($config) ne "HASH") {
		return $config;
	}

    #	
    # In this instance chkA is actually the return code from get_checksum if
    # a failure occurred and chkB will be undefined, if it succeeded its the checksum.
    #
	my ($chkA, $chkB, $version) = get_checksum($file);
	if (! defined $chkB) {
		# We're unable to get a checksum on the file, return the error
		return $chkA;
	}

	if ($$config{$original_file}{"checksum_partA"} eq $chkA &&
            $$config{$original_file}{"checksum_partB"} eq $chkB &&
	    $$config{$original_file}{"version"} eq $version) {
		return SUCCESS($fn, __LINE__);
	}
	else {
		return ERR_FILES_DIFFER($fn, __LINE__, "The install checksum for $original_file does not match $file.");
	}
}

##
# Function:
#	set_HA_as_master
#
# Description:
#	Set a link from /usr/es/sbin/cluster/utilities/clpasswd to /usr/bin/passwd
#	and /bin/passwd
#
# Arguments:
# 	N/A
#
# Returns:
#	SUCCESS() on success, or any ERR_*
#
sub set_HA_as_master() {
	my $config;
	my $fn = "set_HA_as_master";
	my ($passwd_1, $passwd_2) = (PASSWD_BINARY_1(), PASSWD_BINARY_2());
	my ($ret, $clpasswd_loc, $cmd);

	$clpasswd_loc = CLPASSWD_BINARY();

	#
	# If the initial configuration file does not exist, then determine the status
	# of all the passwd binaries and store them in the inital config file
	#
	if (! -f CONFIGURATION_FILE_FIRST() ) {
		create_config(CONFIGURATION_FILE_FIRST());
	}

	# We're going to link the clpasswd binary to /usr/bin/passwd and /bin/passwd
	# taking care to first backup the original versions in ../etc/clpasswd

	$config = create_config();

	if (ref($config) ne "HASH") {
		return $config; # Error resulted in creating the configuration file
	}

	if (defined $$config{$passwd_1}{"filename"} && defined $$config{$passwd_2}{"filename"}) {
		
		#	
		# If the passwd_1 binary is not linked, then we should relink 
		#	
		if ($$config{$passwd_1}{"isLinked"} eq PASSWD_NOT_LINKED_STRING()) {
			$ret = create_safe_link($passwd_1, BACKUP_PASSWD_FILE_LOCATION_1());
			if ($ret != SUCCESS()) { return $ret; }
		}
		else {
			dspmsg(19, "Link already exists from %s to %s on node: %s\n", $passwd_1, $clpasswd_loc, $g_localnodename);
		}
		
		#
		# Usually /bin/passwd and /usr/bin/passwd are hard linked together, we'll
		# recreate the configuration and check to see if its linked now, if it
		# isn't, we'll create a new link and backup the original copy.
		#
		$config = create_config();
		if (ref($config) ne "HASH") {
			dspmsg(2, "ERROR: Unable to create the configuration file, reverting to the AIX system password utility.\n");
			set_AIX_as_master(1); # Revert to the AIX passwd command forcefully if necessary
			return $config;
		}

		if (defined $$config{$passwd_2}{"isLinked"} && $$config{$passwd_2}{"isLinked"} eq PASSWD_NOT_LINKED_STRING()) {
			# This means the /bin/passwd file isn't hard linked to /usr/bin/passwd
			dspmsg(20, "WARNING: %s is not hard linked to %s as expected.\nCreating soft link from %s to %s.\n",
					$passwd_1,
					$passwd_2,
					$passwd_2,
					BACKUP_PASSWD_FILE_LOCATION_2());

			$ret = create_safe_link($passwd_2, BACKUP_PASSWD_FILE_LOCATION_2());
			if ($ret != SUCCESS()) { return $ret; }
		}
	}

	# We need to lock bos.rte.security to avoid accidentally overwriting
	# our link to clpasswd with an update to /usr/bin/passwd

	# If epkg does not exist, create it
	if (! -f '/usr/es/sbin/cluster/etc/clpasswd/passwdLock/passwdLock.epkg.Z') {
		$cmd="rm /usr/es/sbin/cluster/etc/clpasswd/passwdLock/passwdLock.*.epkg.Z 2>/dev/null";
		system($cmd);
		$cmd="/usr/sbin/epkg -w /usr/es/sbin/cluster/etc/clpasswd -e /usr/es/sbin/cluster/etc/clpasswd/ecfile passwdLock > /dev/null 2>&1";
		system($cmd);
		$cmd="mv /usr/es/sbin/cluster/etc/clpasswd/passwdLock/passwdLock.*.epkg.Z /usr/es/sbin/cluster/etc/clpasswd/passwdLock/passwdLock.epkg.Z 2>&1";
		system($cmd);
	}

	# Install epkg to lock bos.security.rte
	$cmd = "/usr/sbin/emgr -e /usr/es/sbin/cluster/etc/clpasswd/passwdLock/passwdLock.epkg.Z > /dev/null 2>&1";
	$ret = system($cmd);

	# If there was a failure, check to see if it was just due to a
	# left over lock.  Attempt to unlock the fileset and re-lock it.
	# We have previously verified that the link does not already
	# exist, so this should be benign.
	if ($ret != 0) {
		$cmd = "/usr/sbin/emgr -r -L passwdLock > /dev/null 2>&1";
		$ret = system($cmd);
		if ($ret == 0) {
			# If the unlock was successful,
			# take another crack at locking it.
			$cmd = "/usr/sbin/emgr -e /usr/es/sbin/cluster/etc/clpasswd/passwdLock/passwdLock.epkg.Z > /dev/null 2>&1";
			$ret = system($cmd);
		}
		if ($ret != 0) {
			dspmsg(17, "ERROR: Unable to lock bos.security.rte, restoring original system passwd\n");
			set_AIX_as_master(1); # Revert to the AIX passwd command forcefully if necessary
			return $ret;
		}
	}

	SUCCESS($fn, __LINE__);
}

##
#
# Function:
#	create_safe_link
#
# Description:
#	Helper function for set_HA_as_master, performs system call to move
#	files and create links.  Handles any resulting errors.
#
# Arguments:
#   $: aix_file - the /bin/passwd or /usr/bin/passwd file
#   $: backup_loc - the location of where the backup should be made
#
# Returns:
#   SUCCESS upon success
#   ERR_* if an error occurs
# 
sub create_safe_link(@) {
	my ($aix_file, $backup_loc) = @_;
	my $fn = "create_safe_link";
	my ($clpasswd_bin) = CLPASSWD_BINARY();
	my ($ret) = 0;
	my ($tcb) = 0;
	
	$ret = system("/usr/bin/mv $aix_file $backup_loc 2>/dev/null");
	if ($ret != 0) {
		dspmsg(21, "ERROR: Unable to move %s to backup location %s.\
Please check your filesystems for available space before attempting this operation\
again.", $aix_file, $backup_loc);
		return ERR_UNABLE_TO_MOVE_TO_BACKUP_LOC($fn, __LINE__, "Unable to move $aix_file to $backup_loc.");
	}
	dspmsg(22, "Creating link from %s to %s on node: %s\n", $aix_file, $clpasswd_bin, $g_localnodename);
	$ret = system("/usr/bin/ln -s $clpasswd_bin $aix_file");

	# Check if TCB is enabled by trying to delete an entry for a non-existent file.
	# As long as TCB is enabled, the attempt to delete a non-existent entry will return 0.
	$tcb = system("tcbck -d /definitelyabadfilename > /dev/null 2>&1");

	if ($ret == 0 && $tcb == 0) {
		# Deleting TCB stanza for /usr/bin/passwd
		$ret = system("tcbck -d /usr/bin/passwd > /dev/null 2>&1");
		# Adding TCB stanza for the link to cl_passwd
		$ret += system("tcbck -a /usr/bin/passwd target=/usr/es/sbin/cluster/utilities/clpasswd > /dev/null 2>&1");
		if ($ret != 0) {
			ERR_UNABLE_TO_LINK_PASSWD($fn, __LINE__, "Unable to modify TCB data for passwd.");
		}
	}

	if ($ret != 0) {
		dspmsg(23, "ERROR: Unable to link %s to HA clpasswd binary %s.\nRestoring %s to default location.",
			$aix_file, $clpasswd_bin);

		# Restore the passwd file back to the default location
		$ret = system("/usr/bin/mv $backup_loc $aix_file 2>/dev/null");
		if ($ret != 0) {
			dspmsg(24, "ERROR: Unable to restore the AIX passwd utility %s, a backup copy is stored\
in %s.\n",
				$aix_file, $backup_loc);
			return ERR_UNABLE_TO_LINK_PASSWD($fn, __LINE__, "Unable to restore $aix_file, backup stored in $backup_loc");
		}
		if ($tcb == 0) {
			# TCB is configured. Remove the stanza for HA's link and restore the stanza for the AIX /usr/bin/passwd
			$ret = system("tcbck -d /usr/sbin/passwd > /dev/null 2>&1");
			$ret += system("tcbck -a /usr/bin/passwd checksum class=apply,inventory,bos.rte.security size mode > /dev/null 2>&1");
			if ($ret != 0) {
				ERR_UNABLE_TO_LINK_PASSWD($fn, __LINE__, "Unable to restore TCB for $aix_file.");
			}
		}
		return ERR_UNABLE_TO_LINK_PASSWD($fn, __LINE__, "Unable to link $aix_file to $clpasswd_bin.");
	}


	return SUCCESS($fn, __LINE__);
}

##
# Function:     restore_backup
#
# Description:
#   Restore a backup of the AIX system passwd command.
#
# Arguments:
#   $: file_to - the location of the restoration /bin/passwd or /usr/bin/passwd
#   $: file_from - the backup file
#   $: forced - force the restoration to occur
#
# Returns:
#   SUCCESS upon successful completion
#   ERR_* if an error occurs
#
sub restore_backup(@) {
	my ($file_to, $file_from, $forced) = @_;
	my $fn = "restore_backup";
	my $config = read_config(CONFIGURATION_FILE());
	my ($diff, $linked, $ret, $cmd);
	
	$diff = check_diff_passwd($file_from, $file_to);
	if ($diff != SUCCESS() && $forced != 1) {
		if ($diff != ERR_FILE_NOT_FOUND()) {
			dspmsg(28, "ERROR: The checksum on the backup %s is invalid, the restore failed for file %s\
on node: %s.\n",
					$file_from,
					$file_to,
					$g_localnodename);
		}
		else {
			dspmsg(29, "ERROR: The backup file %s is\
missing, or corrupt on node: %s.\
Unable to restore AIX passwd command from the backup location.\
Please revert to the base AIX installation copy of %s.\n",
				$file_from,
				$g_localnodename,
				$file_to);
		}
				
		return $diff; # The backup file is not the original backup file, abort
	}

	$linked = readlink $file_to;
	if ($linked eq CLPASSWD_BINARY() || $forced == 1) { # /[usr]/bin/passwd is linked to the clpasswd binary 
		dspmsg(25, "Restoring AIX password utility %s on node: %s\n", $file_to, $g_localnodename);
		$ret = system("/usr/bin/mv $file_from $file_to");

		if ($ret != 0) {
			dspmsg(26, "ERROR: Unable to restore %s from backup location %s, on node %s.\
Please check your filesystems for available space before attempting this\
operation again.",
				$file_to,
				$file_from,
				$g_localnodename);
			return ERR_UNABLE_TO_RESTORE_FROM_BACKUP($fn, __LINE__, "Unable to restore %s from backup location %s.");
		}

		# Check if TCB is enabled by trying to delete an entry for a non-existent file.
		# As long as TCB is enabled, the attempt to delete a non-existent entry will return 0.
		$ret = system("tcbck -d /definitelyabadfilename > /dev/null 2>&1");

		if ($ret == 0) {
			# Deleteing TCB stanzas HA's link to clpasswd
			$ret = system("tcbck -d /usr/bin/passwd > /dev/null 2>&1");
			# Adding TCB stanza for /usr/bin/passwd
                        $ret += system("tcbck -a /usr/bin/passwd checksum class=apply,inventory,bos.rte.security size mode > /dev/null 2>&1");
			if ($ret != 0) {
                        	ERR_UNABLE_TO_RESTORE_FROM_BACKUP($fn, __LINE__, "Unable to restore TCB for $file_to.");
			}
		}

		# Now perform a checksum on the file again to be sure we're safe.
		$diff = check_diff_passwd($file_to, $file_to);
		if ($diff != 0 && $forced != 1) {
			# We should never get here since we already checked the checksum before the move
			# on the backup file, but just in case we'll check again.
			dspmsg(10, "ERROR: The checksum on the file %s is invalid, the restore failed.", $file_to);
			return $diff;
		}
	}

	# unlock bos.rte.security now that original passwd is restored

	$cmd = "/usr/sbin/emgr -r -L passwdLock > /dev/null 2>&1";
	$ret = system($cmd);
	if ($ret != 0) {
		
		dspmsg(18, "WARNING: No lock was found on bos.security.rte\n");
	}

	SUCCESS($fn, __LINE__);
}

##
# Function: set_AIX_as_master
#
# Description:
#   Restores the AIX passwd utility and removes any potential link from
#   /bin/passwd to clpasswd or /usr/bin/passwd to clpasswd
#
# Arguments:
#   $: isForced - force the restoration of /usr/bin/passwd /bin/passwd
#                 to the AIX version
#
# Return:
#   SUCCESS upon successful completion
#   ERR_* if an error occurred.
#
sub set_AIX_as_master($) {
	my ($isForced) = @_; # 0 or 1 = forced
	my $fn = "set_AIX_as_master";
	my ($config, $linked, $diff, $ret, $cmd);
	my ($passwd_1, $passwd_2) = (PASSWD_BINARY_1(), PASSWD_BINARY_2());
	
	$config = create_config(CONFIGURATION_FILE());

	if (ref($config) ne "HASH") { # We couldn't create the configuration file, has
			              # the caller requested that we force the move?
		if (1 != $isForced) {
			return $config;
		}
		$config = ();
	}
	
	if ($$config{$passwd_1}{"isLinked"} eq PASSWD_NOT_LINKED_STRING() &&
            $$config{$passwd_2}{"isLinked"} eq PASSWD_NOT_LINKED_STRING()) {
		dspmsg(27, "AIX password utility %s is already restored on node: %s\n", $passwd_1, $g_localnodename);
		return SUCCESS($fn, __LINE__);
	}

	$ret = restore_backup($passwd_1, BACKUP_PASSWD_FILE_LOCATION_1(), $isForced);
	if ($ret != SUCCESS() && $isForced != 1) {
		return $ret;
	}

	# Build command to remove config.init file if we successfully restore AIX as master
	$cmd =  CONFIGURATION_FILE_FIRST();
	$cmd = "/usr/bin/rm $cmd 2>/dev/null";

	$config = create_config(CONFIGURATION_FILE()); # read in the config afterwards, and determine if we need
				   		       # to restore $passwd_2 if they're hardlinked we shouldn't have to

	if ($$config{$passwd_2}{"isLinked"} eq PASSWD_NOT_LINKED_STRING()) {
		system($cmd);
		return SUCCESS($fn, __LINE__);
	}

	restore_backup($passwd_2, BACKUP_PASSWD_FILE_LOCATION_2(), $isForced);
	if ($ret != SUCCESS() && $isForced != 1) {
		return $ret;
	}
	create_config(CONFIGURATION_FILE());
	system($cmd);

	SUCCESS($fn, __LINE__);
}

##
# Function: create_record
# 
# Description:
#   Create a checksum record for the specified file
#
# Arguments:
#   $: filename - name of the file to create a checksum record for
#
# Return:
#   (SUCCESS, scalar representation of checksum record)
#   or ERR_FILE_NOT_FOUND if unable to open specified filename
#
sub create_record($) {
	my ($filename) = @_;
	my $fn = "create_record";
	my $record;
	my $linked;
	my $configuration_file = CONFIGURATION_FILE();
	my ($chkA, $chkB, $version);

	$record = $filename . ":";
	($chkA, $chkB, $version) = get_checksum($filename);
	if (! defined $chkB) {
		return ERR_FILE_NOT_FOUND($fn, __LINE__, "unable to open $filename in call to get_checksum");
	}

	if (! defined $version) { $version = ""; }

	$record .= "$chkA:$chkB:$version:$^T";

	# Determine if the file is linked
	$linked = readlink $filename;
	if (! defined $linked) {
		$record .= "::" . PASSWD_NOT_LINKED_STRING();
	}
	else {
		$record .= ":$linked:" . PASSWD_LINKED_STRING();
	}
	return (SUCCESS($fn, __LINE__), $record);
}

##
# Function: create_config
#
# Description:
#   Create a configuration file with name specified as argument
#   containing a checksum of the AIX password commands, the
#   clpasswd command and the backup locations for the AIX password
#   commands
#
# Arguments:
#   $: config_file - the full path to the configuration file
#
# Returns:
#   SUCCESS - upon successful execution
#   ERR_* - if an error occcured
#
sub create_config($) {
	my ($config_file) = @_;
	my $fn = "create_config";
	my ($filename, $ret, $record);
	my (@filelist);

	push @filelist, PASSWD_BINARY_1();
	push @filelist, PASSWD_BINARY_2();
	push @filelist, BACKUP_PASSWD_FILE_LOCATION_1();
	push @filelist, BACKUP_PASSWD_FILE_LOCATION_2(); 
	push @filelist, CLPASSWD_BINARY();

	if (! defined $config_file) {
		$config_file = CONFIGURATION_FILE();
	}

	open(CONFIG_FILE, "> $config_file") || return ERR_UNABLE_TO_CREATE_CONFIG_FILE($fn, __LINE__, "Unable to open configuration file $config_file for writing.");
	foreach $filename (@filelist) {
		($ret, $record) = create_record($filename);
		if ($ret == SUCCESS()) {
			print CONFIG_FILE $record . "\n";
		}
	}
	return read_config($config_file);
}

##
# Function: get_checksum
#
# Description:
#   Computes a checksum for the specified file
#
# Arguments:
#   $: file - name of the file to compute a checksum for (fullpath)
#
# Returns:
#   ($chkA, $chkB, $version) - where
#       chkA - is major checksum (from sum command)
#       chkB - is minor checksum (from sum command)
#       version - is the version of passwd.c in the AIX implimentation
#
# or alternatively if an error occurs will return:
#   ERR_*
#    
sub get_checksum($) {
	my ($file) = @_;
	my $fn = "get_checksum";
	my ($chkA, $chkB, $version);

	if (! -f $file) {
		return ERR_FILE_NOT_FOUND($fn, __LINE__, "Unable to open $file.");
	}

	open(CMD, "/usr/bin/sum $file |");
	($chkA, $chkB, $_) = split(/\s{1,}/, <CMD>);
	close(CMD);
	
	open(CMD, "/usr/bin/what $file |");
	foreach (<CMD>) {
		if (/\s{1,}\d{1,}\s*([\d\.]{1,})\s*.+passwd\.c.+/) {
			$version = $1;
		}
	}
	close(CMD);
	if (! defined $version) { $version = ""; }
	logmsg($fn, __LINE__, "created checksum $chkA,$chkB $version for file $file");
	return ($chkA, $chkB, $version);
}

##
# Function: read_config
#
# Description:
#	Reads in the configuration file passed in as the first argument to
#	this function, or by default loads the config file name stored in
#	CONFIGURATION_FILE.  In either case it parses the output and places
#	the data into a hash of hashes.
#
# Arguments:
#   $: file - configuration file to read in
#
# Return:
#   %: hash representation of configuration
#   or
#   ERR_* if an error occurs
#
sub read_config($) {

	my ($file) = @_;
	my ($input, @records);

	if (! defined $file) { $file = CONFIGURATION_FILE(); }

	open(CONFIG_FILE, "< $file") || return ERR_UNABLE_TO_OPEN_CONFIG_FILE();
	@records = <CONFIG_FILE>;
	close(CONFIG_FILE);

	return parse_config(@records);
}

##
# Function: parse_config
# 
# Description:
#   Parse the configuration passed in as an array of records
#
# Arguments:
#   @: the list of records
#
# Returns:
#   %:  hash representation of the configuration
#
sub parse_config($) {
	my @records = @_;
	my $fn = "parse_config";
	my (@keys) = CONFIG_FILE_KEYS(); 
	my ($index, $record, $value, $root_key, %config, $config_as_string);

	foreach $record (@records) {
		$root_key = "";
		$index = 0;
		foreach $value (split(/:/, $record)) {
	
			$value =~ s/\n//g;
	
			if ($index == 0) {
				$root_key = $value;
			}

			logmsg($fn, __LINE__, "$root_key." . $keys[$index] . " = $value");

			$config{$root_key}{$keys[$index]} = $value;
			$index++;
		}
	}
	return \%config;
}

##
# Function:     dump_config
#
# Description:
#   Debugging function to dump the configuration to the screen.
#
# Arguments:
#   n/a
#
# Return:
#   n/a
#
sub dump_config() {
	my $fn = "dump_config";
	open(FILE, "< " . CONFIGURATION_FILE()) || return ERR_UNABLE_TO_OPEN_CONFIG_FILE($fn, __LINE__);
	print <FILE>;
	close(FILE);

	SUCCESS_AND_EXIT($fn, __LINE__);
}

##
# Function: smit_isLinked
#
# Description:
#   This function is called from smit to determine if the AIX passwd
#   utility is linked to the clpasswd (HA) utility
#   prints PASSWD_LINKED_STRING if linked
#   or PASSWD_NOT_LINKED_STRING if not linked
#
# Arguments:
#   n/a
#
# Returns:
#   SUCCESS_AND_EXIT - upon successful complete
#   ERR_* if an error occurs
# 
sub smit_isLinked() {
	my $config_file = CONFIGURATION_FILE();
	my $fn = "smit_dump_config";
	my $config = read_config($config_file);
	my ($passwd_1, $passwd_2) = (PASSWD_BINARY_1(), PASSWD_BINARY_2());
	
	if (ref($config) ne "HASH") {
		return ERR_UNABLE_TO_READ_CONFIGURATION_FILE($fn, __LINE__, "Unable to read configuration file $config_file.");
	}

	if ($$config{$passwd_1}{"isLinked"} eq PASSWD_LINKED_STRING() && 
            $$config{$passwd_2}{"isLinked"} eq PASSWD_LINKED_STRING()) {
		print PASSWD_LINKED_STRING();
	}
	else {
	    	if ($$config{$passwd_1}{"isLinked"} ne $$config{$passwd_2}{"isLinked"}) {
			print PASSWD_UNKNOWN_STRING();
		}
		else {
			print PASSWD_NOT_LINKED_STRING();
		}
	}
	print "\n";
	SUCCESS_AND_EXIT($fn, __LINE__);
}

##
# Function: smit_discovery
#
# Description:
#   Prints the #passwd\n for the smit discovery command to determine if
#   the passwd command is linked or unlinked.
#
# Arguments:
#   n/a
#
# Returns:
#   n/a
#
sub smit_discovery() {
	print "#passwd\n";
	smit_isLinked();
}

##
# Function: distribute_change
#
# Description:
#   Distribute a change to the AIX passwd command to multiple nodes
#   in the cluster.
#
# Arguments:
#   $: group - name of the resource group to determine where to distribute change
#   $: only_exec_locally - don't attempt to distribute change any further than this node if true
#   $: call_func - function to invoke that will change the AIX passwd command
#   @: args - any arguments to the function call_func
#
# Returns:
#   SUCCESS_AND_EXIT - upon successful execution
#   ERR_* if an error occurred
#
sub distribute_change(@) {
	my ($group, $only_exec_locally, $call_func, @args) = @_;
	my $fn = "distribute_change";
	my $passwd_1 = PASSWD_BINARY_1();
	my $cmd_get_all_nodes = CMD_GET_ALL_NODES();
	my ($ret, $config, $flag, $cmd, $node, @nodes);
	
	$ret = &$call_func(@args);
	if ($ret != SUCCESS()) {
		return $ret;
	}

	if (defined $only_exec_locally) {
		return SUCCESS_AND_EXIT($fn, __LINE__);
	}

	$config = read_config(CONFIGURATION_FILE());
	if (ref($config) ne "HASH") {
		return $config;
	}

	if ($$config{$passwd_1}{"isLinked"} eq PASSWD_LINKED_STRING()) {
		$flag = "-l"; # Link /bin/passwd to clpasswd
	}
	else {
		$flag = "-r"; # Remove the link from /usr/bin/passwd to clpasswd
	}

	if (defined $group && $group ne "") {
		@nodes = split(/\s/, `/usr/es/sbin/cluster/utilities/clgetgrp -g $group -f nodes`);
	}
	else {
		@nodes = `$cmd_get_all_nodes`;
	}

	foreach $node (@nodes) {
		$node =~ s/\n//g;
		if ($g_localnodename eq $node) { next; }
		$cmd = "/usr/es/sbin/cluster/utilities/cl_rsh $node \"/usr/es/sbin/cluster/utilities/cl_chpasswdutil $flag";
		if (defined $group && $group ne "") {
			$cmd .= " -g $group";
		}
		$cmd .= " -o";
		$cmd .= "\"";
		logmsg($fn, __LINE__, "Executing command:\n$cmd");
		$ret = system($cmd);
		logmsg($fn, __LINE__, "returned $ret");
		if ($ret != 0) {
			if ($flag eq "-l") {
				dspmsg(12, "ERROR: Unable to create link on node: %s.\n", $node);
			}
			else {
				dspmsg(13, "ERROR: Unable to restore /usr/bin/passwd on node: %s, an error occurred while\
attempting to communicate with the node.  Please check to ensure the node is available.\n", $node);
			}
			return ERR_UNABLE_TO_DISTRIBUTE_CHANGE($fn, __LINE__, "Unable to distribute change on $node\n($cmd).");
		}
	}
	
	SUCCESS_AND_EXIT($fn, __LINE__);
}

##
# Function: MainFunc
#
# Description:
#   Main Function for this script
#
sub MainFunc() {
	my %options;
	my $fn = "MainFunc";
	my ($config, $val, $ret);
	my $args = join(" ", @ARGV);

	if (!isRootUser()) {
		dspmsg(14, "ERROR: The SMIT menu chosen can only be accessed by\
the system administrator.\n");
		exit(-1);
	}

	logmsg($fn, __LINE__, "$0 Invoked with arguments $args");

	#
	# If the file containing current configuration does not exist, then determine
	# the status of all the passwd binaries and store them in the inital config file
	#
	if (! -f CONFIGURATION_FILE() ) {
		create_config(CONFIGURATION_FILE());
	}

	#############
	# Valid Arguments
	#
	# -g <group> 	optional, resource group where /bin/passwd /usr/bin/passwd should be changed
	# -r		Restore the /bin/passwd command to original location
	# -l		Link /bin/passwd command to /usr/es/sbin/cluster/utilities/clpasswd
	# -c		Check to make sure that nodes in the cluster have the same version of clpasswd
	#		can be used with -g
	# -s		Print settings for the local node for consumption by smit
	# -t		Print configuration on local node
	# -o		Don't distribute this change to other nodes in the cluster

	getopts('dotrlcsg:', \%options); # Process single-character switches
                                     	# with switch clustering
	
	foreach $val (sort keys %options) {
		if ($val eq "d") { $ret = smit_discovery(); }
		if ($val eq "s") { $ret = smit_isLinked(); }
		if ($val eq "r") { $ret = distribute_change($options{"g"}, $options{"o"}, \&set_AIX_as_master, 0); }
		if ($val eq "l") { $ret = distribute_change($options{"g"}, $options{"o"}, \&set_HA_as_master); }
		if ($val eq "t") { $ret = dump_config(); }

		if (! defined $ret) { $ret = SUCCESS(); }
		
		if ($ret eq SUCCESS_AND_EXIT()) {
			exit(0);
		}
	}
}

MainFunc();
