#!/usr/bin/perl -w
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# 61haes_r714 src/43haes/usr/sbin/cluster/utilities/cl_manageallowpasswd.sh 1.15 
#  
# Licensed Materials - Property of IBM 
#  
# COPYRIGHT International Business Machines Corp. 2004,2009 
# 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 
# @(#)83	1.3 src/43haes/usr/sbin/cluster/utilities/cl_manageallowpasswd.sh, hacmp.utils, 51haes_r520 1/14/04 20:02:41
# @(#)83      1.15 src/43haes/usr/sbin/cluster/utilities/cl_manageallowpasswd.sh, hacmp.utils, 61haes_r714 1/6/09 06:12:00
use strict;
use Getopt::Std;

# vi set ts=8

##############################################################################
#
#
# cl_manageallowpasswd - utility script for handling adding / removing users
# 			 from the allow_passwd file.
#
##############################################################################



sub ALLOW_USERS_FILE() 	{ "/usr/es/sbin/cluster/etc/clpasswd/cl_passwd_users" };
sub SYSTEM_USERS_FILE() { "/usr/es/sbin/cluster/etc/clpasswd/sysusers" };
sub GET_LOCAL_NODENAME(){ "/usr/es/sbin/cluster/utilities/get_local_nodename" };
sub ALL_USERS_TOKEN()   { "ALL_USERS" };
sub ODMDIR()		{ "/etc/es/objrepos" };


my %global;

##############################################################################
#
# Error codes returned from functions
#
sub ERR_UNABLE_TO_OPEN_ETC_PASSWD() 		{ 1 };
sub ERR_UNABLE_TO_OPEN_ALLOWED_USERS()		{ 2 };
sub ERR_DEPRECATED				{ 3 };
sub ERR_INVALID_USER_SPECIFIED()		{ 4 };
sub ERR_INVALID_ARGUMENTS()			{ 5 };
sub ERR_NO_NODES_AVAILABLE()			{ 6 };
sub ERR_AND_EXIT()				{ 7 };

sub SUCCESS() 					{ 0 };
sub SUCCESS_AND_EXIT()				{ 999 };

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


##
#
# Function:
#	add_users
#
# Description:
#	Add the list of users defined as the argument to this
#	function, only the list of users passed in will be
#	allowed users.
#	As each user is added, the function will print out the
#	name of user to the stdout.
#
# Arguments:
#   $: users_in - list of users allowed to change password (complete list)
#   $: sync_flag - synchronize these changes to other nodes in the cluster
#
# Return:
#   SUCCESS_AND_EXIT - upon succesful addition
#   ERR_* - upon encountering an error
#
sub add_users(@) {
	my ($users_in, $sync_flag) = @_;
	my $file = ALLOW_USERS_FILE();
	my @users;
	my $all_users_token = ALL_USERS_TOKEN();

        if (!defined $users_in) {
                $users_in = "";
                open(FILE, "> $file") || return ERR_UNABLE_TO_OPEN_ALLOWED_USERS();
                print "\n";
                close(FILE);
                if ($sync_flag != 1) {
                        sync_allowusers_file($users_in);
                }
                return SUCCESS_AND_EXIT();
        }

	$users_in =~ s/'//g;

	if ($sync_flag != 1) {
		@users = split(/\s/, $users_in);
	}
	else {
		@users = split(/\:/, $users_in);
	}

	if ($users_in =~ /$all_users_token/) {
		# User specified the all users token, check to see if it
		# already exists, if not append it to the file
		
		# Check to make sure no other users were specified
		if ($users_in !~ /^\s*$all_users_token\s*$/) {
			return ERR_INVALID_USER_SPECIFIED();
		}
	}
	open(FILE, "> $file") || return ERR_UNABLE_TO_OPEN_ALLOWED_USERS();
	foreach (@users) {
		if ($_ eq $all_users_token) {
			print FILE "+\n";
			dspmsg(200, "Allowing all cluster users to change password cluster wide.\n") if ($sync_flag != 1);
		}
		else {
			dspmsg(201, "Adding user %s\n", $_) if ($sync_flag != 1);
			print FILE $_ . "\n";
		}
	}
	close(FILE);
	if ($sync_flag != 1) {
		sync_allowusers_file($users_in);
	}

	SUCCESS_AND_EXIT();
}

##
# Function: sync_allowusers_file
#
# Description:
#   Synchronize the contents of the allow users password file across all specified nodes
#   in the cluster. Invokes the cl_manageallowpasswd perl script on the remote nodes with
#   a list of users to add.
#
# Arguments:
#   $: users_in - list of users to add cluster wide
#
# Return:
#   SUCCESS_AND_EXIT - if successful
#   ERR_* - if an error occurs
#
sub sync_allowusers_file($) {

	my ($users_in) = @_;
	my @nodes = `/usr/es/sbin/cluster/utilities/clnodename`;
	my ($localnode, $node, $ret, $cmd);

	open(CMD, GET_LOCAL_NODENAME() . " |");
	$localnode = <CMD>;
	close(CMD);
	$localnode =~ s/\s//g;

	$users_in =~ s/\s/\:/g;

	foreach $node (@nodes) {
		$node =~ s/\s//g;
		if ($node eq $localnode) { next; }
		$cmd = "/usr/es/sbin/cluster/utilities/cl_rsh $node /usr/es/sbin/cluster/utilities/cl_manageallowpasswd -s '$users_in'";
		$ret = system($cmd);
		if ($ret != 0) {
			dspmsg(202, "Node %s could not be contacted, please ensure the clcomdES sub-system is\
running properly and that the /usr/es/sbin/cluster/etc/rhosts file is properly configured.\n\n", $node);
		}
		else {
			dspmsg(203, "Synchronized allowed user data to node: %s.\n", $node);	
		}
	}
}


##
#
# Function:
#	list_all_cluster_users
#
# Description:
#	List all users defined to the list. Indirectly invoked by
#	list_all_cluster_users_smit via a call to cl_rsh $node ...
#
# Arguments:
#	N/A
#
# Return:
#   SUCCESS_AND_EXIT 
#
sub list_all_cluster_users() {

	my ($username, $users);
	open(FILE, "< /etc/passwd") || return ERR_UNABLE_TO_OPEN_ETC_PASSWD();
	foreach (<FILE>)
	{
		$username = (split(/:/))[0];
		$users .= "$username:";
	}
	print $users . "\n";

	SUCCESS_AND_EXIT();
}


##
#
# Function:
#       list_all_cluster_users_smit_ra
#
# Description:
#       List all cluster users for SMIT by invoking the list_all_cluster_users
#       function (above) indirectly via cl_rsh on each node and then sifting
#       out user names that are not common among all nodes.
#       This function will also filter out user names that are system users
#       as defined in /usr/es/sbin/cluster/etc/sysusers
#       And this function ignores root and adm users
#
# Arguments:
#       N/A
#
# Return:
#   SUCCESS_AND_EXIT - upon successful execution
#   ERR_* - if an error occurs
#
sub list_all_cluster_users_smit_ra() {

        my ($node, $user, $sysuser, $match, $matches);
        my ($userA, $userB, $nodeA, $nodeB);
        my (@users, @nonsys_users, @common_users);
        my (@nodes) = `/usr/es/sbin/cluster/utilities/clnodename`;
        my (%usersByNode);

        if ($#nodes < 0) {
                return ERR_NO_NODES_AVAILABLE();
        }


        #
        # Collect the list of users for each node
        #
        foreach $node (@nodes) {
                $node =~ s/\s//g;
                @users = split(/:/, `/usr/es/sbin/cluster/utilities/cl_rsh $node /usr/es/sbin/cluster/utilities/cl_manageallowpasswd -P 2>/dev/null`);
                # Filter out any system users that may appear
                @nonsys_users = ();
                foreach $user (@users) {
                        $match = 0;
                        foreach $sysuser (@{$global{"SYSTEM_USERS"}}) {
                                $sysuser =~ s/\s//g;
                                if ($user eq $sysuser) { $match = 1; }
                                if ($user eq "root" or $user eq "adm") { $match = 0; }
                        }
                        if ($match != 1) {
                                push @nonsys_users, $user;
                        }
                }
                @{$usersByNode{$node}} = @nonsys_users;
        }

        # Now determine which users are common amongst all the nodes
         $nodeA = $nodes[0];
         if ($#nodes > 0) {
                 foreach $userA (@{$usersByNode{$nodeA}}) {
                         $matches = 0;

                         foreach $nodeB (@nodes) {
                                 if ($nodeA eq $nodeB) { next; }
                                 $match = 0;
                                 foreach $userB (@{$usersByNode{$nodeB}} ) {
                                        if ($userA eq $userB) { $match = 1; }
                                 }
                                 if ($match == 1) { $matches++; }
                         }
                         if ($matches == $#nodes) { push @common_users, $userA; }
                 }
         }
         else {
                  @common_users = @{$usersByNode{$nodeA}};
         }

        print ALL_USERS_TOKEN();
        print "\n";
        foreach (@common_users) {
                if (!/^\s*$/) { print $_ . "\n" };
        }
}




##
#
# Function:
#	list_all_cluster_users_smit
#
# Description:
#	List all cluster users for SMIT by invoking the list_all_cluster_users
#	function (above) indirectly via cl_rsh on each node and then sifting
#	out user names that are not common among all nodes.
#	This function will also filter out user names that are system users
#	as defined in /usr/es/sbin/cluster/etc/sysusers
#
# Arguments:
#	N/A
#
# Return:
#   SUCCESS_AND_EXIT - upon successful execution
#   ERR_* - if an error occurs
#
sub list_all_cluster_users_smit() {

	my ($node, $user, $sysuser, $match, $matches);
	my ($userA, $userB, $nodeA, $nodeB);
	my (@users, @nonsys_users, @common_users);
	my (@nodes) = `/usr/es/sbin/cluster/utilities/clnodename`;
	my (%usersByNode);

	if ($#nodes < 0) {
		return ERR_NO_NODES_AVAILABLE();
	}


	#
	# Collect the list of users for each node
	#
	foreach $node (@nodes) {
		$node =~ s/\s//g;
		@users = split(/:/, `/usr/es/sbin/cluster/utilities/cl_rsh $node /usr/es/sbin/cluster/utilities/cl_manageallowpasswd -P 2>/dev/null`);
		# Filter out any system users that may appear
		@nonsys_users = ();
		foreach $user (@users) {
			$match = 0;
			foreach $sysuser (@{$global{"SYSTEM_USERS"}}) {
				$sysuser =~ s/\s//g;
				if ($user eq $sysuser) { $match = 1; }
			}
			if ($match != 1) {
				push @nonsys_users, $user;
			}
		}
		@{$usersByNode{$node}} = @nonsys_users;
	}
	
	# Now determine which users are common amongst all the nodes
	$nodeA = $nodes[0];
	if ($#nodes > 0) {
		foreach $userA (@{$usersByNode{$nodeA}}) {
			$matches = 0;

			foreach $nodeB (@nodes) {
				if ($nodeA eq $nodeB) { next; }
				$match = 0;
				foreach $userB (@{$usersByNode{$nodeB}} ) {
					if ($userA eq $userB) { $match = 1; }
				}
				if ($match == 1) { $matches++; }
			}
			if ($matches == $#nodes) { push @common_users, $userA; }
		}
	}
	else {
		@common_users = @{$usersByNode{$nodeA}};
	}

	print ALL_USERS_TOKEN();
	print "\n";
	foreach (@common_users) {
		if (!/^\s*$/) { print $_ . "\n" };
	}
}


##
#
# Function:
#	list_all_allowed_users
#
# Description:
#	List all allowed users for SMIT consumption
#
# Arguments:
#   N/A
#
# Return:
#   SUCCESS_AND_EXIT	
sub list_all_allowed_users() {
	
	my $users = "";
	my $file = ALLOW_USERS_FILE();
	my $all_users_token = ALL_USERS_TOKEN();
	
	open(FILE, "< $file") || return ERR_UNABLE_TO_OPEN_ALLOWED_USERS();
	foreach (<FILE>) {
		s/\s//g;
		$users .= "$_ ";
	}
	if ($users =~ /\+/) {
		$users = $all_users_token;
	}

	$users =~ s/\s$//;  # Remove trailing space

	print "#users\n";
	print "$users\n";

	SUCCESS_AND_EXIT();
}

##
#
# Function:
#	usage
#
# Description:
#	Prints a usage statement for the script, this is not intended to be
#	used by end-customers thus the usage statement is not ILS compliant.
#   This function will terminate the running perl script with exit(-1)
#
# Arguments:
#   N/A
#
# Return:
#   N/A
#
sub usage() {
	my $progname = $0;

	$progname =~ s/^.+\/([^\/]{1,})$/$1/;

	print "\n\n$progname: Manage the allowed password database\n\n";
	print "-V 	SMIT output listing all available cluster users.\n";
	print "-L	SMIT output lising all allowed users.\n";
	print "-a users...	add users to the allowed users list.\n";
	print "-s users...	synchronize users to another node.\n";
	print "-h		this usage screen.\n";
	print "\n\n";

	exit(1);
}

##
# Function: dspmsg
#
# Description:
#   Invokes the dspmsg command with the arguments specified below (ILS function)
#
# Arguments:
#   $: id - message catalog id for cspoc.cat (set 124)
#   $: msg - message as string to display for LC_ALL=C
#   @: args - arguments to provide to message string
#
# Return:
#   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: print_error
#
# Description:
#   Print an error message based on the error code
#
# Arguments:
#   $: error code
#
# Return:
#   SUCCESS
#
sub print_error($) {

	my $val = $_[0];

	if ($val == ERR_INVALID_USER_SPECIFIED()) {
		dspmsg(204,
"ERROR: Please select one or more users to authorize password change\
across the cluster, or select only the ALL_USERS option to allow all\
cluster users to change their password cluster wide.\n"); }

	if ($val == ERR_UNABLE_TO_OPEN_ETC_PASSWD()) {
		dspmsg(205,
"ERROR: Unable to open /etc/passwd, this operation can only be\
performed by a user with root privileges.  Please logout and\
log back in as a user with such authority before trying this\
operation again, or contact your system administrator for\
further details.\n"); }

	if ($val == ERR_UNABLE_TO_OPEN_ALLOWED_USERS()) {
		dspmsg(206,
"ERROR: Unable to open the allowed users database.  This operation\
can only be performed by a user with root privileges.  Please\
logout and log back in as a user with such authority before trying\
this operation again, or contact your system administrator for\
further details.\n"); }

	if ($val == ERR_NO_NODES_AVAILABLE()) {	
		dspmsg(207,
"No cluster nodes are defined on the local node.  Please check\
your cluster configuration before attempting this operation again.\n");
	}

	SUCCESS();
}

##
#
# Function:
#	modify_hafiles
#
# Description:
#	Modifies the HA files we use for allowing users to change passwords
# 
# Arguments:
#	N/A
#
# Returns:
# 	SUCCESS(), or ERR_AND_EXIT()
#
sub modify_hafiles() {

	if ( system("/usr/bin/touch " . ALLOW_USERS_FILE() ) != 0) {
		dspmsg(208, "Aborting could not touch file %s.\n",
			     ALLOW_USERS_FILE() );

		return ERR_AND_EXIT();
	}
	
	if ( system("/usr/bin/chown root.system " . ALLOW_USERS_FILE()) != 0) {
		dspmsg(209, "Aborting could not change ownership of %s file.\n",
			     ALLOW_USERS_FILE());

		return ERR_AND_EXIT();
	}

	if ( system("/usr/bin/chmod 600 " . ALLOW_USERS_FILE()) != 0) {
		dspmsg(210, "Aborting could not change mode of %s file.\n",
			     ALLOW_USERS_FILE());
		return ERR_AND_EXIT();
	}

	SUCCESS();
}

##
#
# 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 $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 = $>;

		#print "Real      UID = $real_uid GID = $real_gid\n";
		#print "Effective UID = $eff_uid GID = $eff_gid\n";

		# 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:
#	MainFunc
#
# Description:
#	Main function for this script, starting point. Parses cmdline options
#	and invokes sub routines for performing requested operaton(s)
# 
# Arguments:
#	N/A
#
sub MainFunc() {
	my $val = 999;
        my $ret = 999;
	my %options;

	# Read in list of system users to exclude from list of available
	# cluster users
	#
	open(FILE, "< " . SYSTEM_USERS_FILE());
	@{$global{"SYSTEM_USERS"}} = <FILE>;
	close(FILE);

	# Is the user of the correct authority to run this command?
	if (isRootUser() != 1) {
		dspmsg(211, "ERROR: The SMIT menu chosen can only be accessed by\
the system administrator.\n");
		exit(-1);
	}

	# Setup files we rely upon
	if (modify_hafiles() == ERR_AND_EXIT()) { exit(-1); }
	
	# Parse incoming arguments
	getopts('hPVLa:R:s:', \%options); # Process single-character switches
				     # with switch clustering

	foreach $val (sort keys %options) {
		if ($val eq 'a') { $ret = add_users($options{$val}, \%options); }
           	elsif ($val eq 'P') { $ret = list_all_cluster_users(); }
                elsif ($val eq 'V') { $ret = list_all_cluster_users_smit(); }
                elsif ($val eq 'R') { $ret = list_all_cluster_users_smit_ra(); }
               	elsif ($val eq 'L') { $ret = list_all_allowed_users(); }
		elsif ($val eq 's') { $ret = add_users($options{$val}, 1); }
		elsif ($val eq 'h') { usage(); }
       	}
        if (!defined $ret) {
                exit(0);
        }
       	if ($ret == SUCCESS_AND_EXIT()) {
               	exit(0);
       	}
       	elsif ($ret != SUCCESS()) {
               	print_error($ret);
               	exit(-1);
       	}
}


MainFunc();
