#!/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,}/, ); close(CMD); open(CMD, "/usr/bin/what $file |"); foreach () { 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 = ; 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 ; 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 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();