#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# bos720 src/bos/usr/bin/cdat/cdat-access.pl 1.6 
#  
# Licensed Materials - Property of IBM 
#  
# COPYRIGHT International Business Machines Corp. 2010,2011 
# 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 
# @(#)19    1.6  src/bos/usr/bin/cdat/cdat-access.pl, cdat, bos720 7/15/11 04:01:14
use warnings;
use strict;
use Getopt::Long;
use cdat;
use messages;

#
# Constants.
#
my $USER = 'cdat';

#
# Globals.
#
my $delete;
my $pubkey = "";

######################################################################
# Function:	usage
# Purpose:	Display usage.
# Tasks:	Print usage and exit.
# Input:	None
# Output:	None
######################################################################
sub usage
{
    printf(STDERR catgets(MSG_CDAT_ACCESS_USAGE,
        "Usage: cdat access -h\n".
	"       cdat access [-dF] [-u User] -n Type:[User@]Node ...\n".
	"       cdat access [-dF] [-u User] -f File ...\n"));
    exit(1);
}

######################################################################
# Function:	lpar_install
# Purpose:	Initialize access to an AIX LPAR.
# Tasks:	Initialize access to an AIX LPAR.
# Input:	hostname, user to create, password
# Output:	0 on success, remote command exit status on failure
######################################################################
sub lpar_install
{
    my ($name, $user, $passwd) = @_;

    # List of RBAC authorizations that are needed for the default collect
    # types on AIX (perfpmr, snap, trace, psrasinit, psrasremove):
    my $AUTH = "aix.fs.stat,aix.lvm.perf.tune,".
"aix.network.config.tcpip,aix.network.daemon,aix.proc,aix.ras.debug,".
"aix.ras.dump,aix.ras.error,aix.ras.trace,aix.security.audit,".
"aix.system.boot.info,aix.system.config.diag,aix.system.config.dlpar,".
"aix.system.config.odm,aix.system.config.perf,aix.system.config.src,".
"aix.system.config.wlm,aix.system.install,aix.system.stat,".
"aix.wpar.system.resources,aix.system.debug,ibm.ps.client.bind";

    my $cmd = <<EOF
# check that we are root
[ \`id -u\` != "0" ] && exit 1

# check if the CdatClient RBAC role already exists
/usr/sbin/lsrole CdatClient > /dev/null 2>&1
[ \$? -ne 0 ] && {
    # check if the ibm.ps.client.bind authorization exists
    /usr/sbin/lsauth ibm.ps.client.bind > /dev/null 2>&1
    [ \$? -ne 0 ] && {
        # create the authorization hierarchy if it does not exist
	# NB: that mail fail if it already exists but errors will
	#     be caught below when creating ibm.ps.client.bind
        (/usr/bin/mkauth ibm
         /usr/bin/mkauth ibm.ps
         /usr/bin/mkauth ibm.ps.client) > /dev/null 2>&1
        # create the bind authorization
        /usr/bin/mkauth ibm.ps.client.bind > /dev/null 2>&1
        [ \$? -ne 0 ] && exit 2
    }
    # create our CDAT role now that all authorizations exist
    /usr/bin/mkrole authorizations=${AUTH} CdatClient
    [ \$? -ne 0 ] && exit 3

    # set entries in the kernel security tables
    /usr/sbin/setkst -q -t role,auth > /dev/null 2>&1
}
# check if the user already exists
/usr/sbin/lsuser "$user" > /dev/null 2>&1
[ \$? -ne 0 ] && {
    # create the user with the CdatClient RBAC role
    /usr/bin/mkuser roles=CdatClient default_roles=CdatClient "$user" > /dev/null 2>&1
    [ \$? -ne 0 ] && exit 4
    # set the initial password
    echo "$user:$passwd" | /usr/bin/chpasswd -f NOCHECK
    [ \$? -ne 0 ] && exit 5
    # create the .cdat file indicating that we created this user ourselves
    /usr/bin/su $user "-c touch ~$user/.cdat" > /dev/null 2>&1
}
# retrieve user home directory (~user does not always work!)
homedir=`/usr/sbin/lsuser -a home $user | cut -d= -f2`
[ -z "\$homedir" ] && exit 6
[ ! -d "\$homedir/.ssh" ] && {
    # create the .ssh directory
    /usr/bin/su $user "-c mkdir -p \\\"\$homedir/.ssh\\\"" > /dev/null 2>&1
    [ \$? -ne 0 ] && exit 7
}
# always set correct permissions for the .ssh directory
/usr/bin/su $user "-c chmod 700 \\\"\$homedir/.ssh\\\"" > /dev/null 2>&1
[ ! -f "\$homedir/.ssh/authorized_keys2" ] && {
    # create an empty authorized_keys2 file
    /usr/bin/su $user "-c touch \\\"\$homedir/.ssh/authorized_keys2\\\"" > /dev/null 2>&1
    [ \$? -ne 0 ] && exit 8
}
# always set correct permissions for the authorized_keys2 file
/usr/bin/su $user "-c chmod 600 \\\"\$homedir/.ssh/authorized_keys2\\\"" > /dev/null 2>&1
[ ! -z "$pubkey" ] && {
    # check if our SSH public key is already installed
    /usr/bin/grep -q "$pubkey" "\$homedir/.ssh/authorized_keys2"
    [ \$? -ne 0 ] && {
        # install our SSH public key
        echo "$pubkey" >> "\$homedir/.ssh/authorized_keys2"
	[ \$? -ne 0 ] && exit 9
    }
}
exit 0
EOF
;

    my @lines = print cdat::remote_cmd('root', $name, $cmd,
        $cdat::REMOTE_ASKUSER | $cdat::REMOTE_PROBE);
    DEBUG(1, @lines);
    return $?;
}

######################################################################
# Function:	lpar_uninstall
# Purpose:	Remove access to an AIX LPAR.
# Tasks:	Remove access to an AIX LPAR.
# Input:	hostname, user to remove
# Output:	0 on success, remote command exit status on failure
######################################################################
sub lpar_uninstall
{
    my ($name, $user) = @_;

    my $cmd = <<EOF
# check that we are root
[ \`id -u\` != "0" ] && exit 1

if [ -f ~$user/.cdat ]; then
    # remove the user and its home directory
    /usr/sbin/userdel -r $user > /dev/null 2>&1
elif [ ! -z "$pubkey" -a -f ~$user/.ssh/authorized_keys2 ]; then
    /usr/bin/su $user "-c touch ~$user/.ssh/authorized_keys2.bak"
    [ \$? -eq 0 ] && {
        chmod 600 ~$user/.ssh/authorized_keys2.bak
        [ \$? -eq 0 ] && {
            # remove our SSH public key
            /usr/bin/grep -v "$pubkey" ~$user/.ssh/authorized_keys2 > ~$user/.ssh/authorized_keys2.bak
            cat ~$user/.ssh/authorized_keys2.bak > ~$user/.ssh/authorized_keys2
            rm -f ~$user/.ssh/authorized_keys2.bak
	}
    }
fi
# remove the CdatClient RBAC role if it is no longer used
/usr/sbin/lsuser ALL | grep -wq CdatClient
[ \$? -ne 0 ] && {
    /usr/bin/lslpp -lq pureScale.client.rte 2>/dev/null | grep -wq COMMITTED
    [ \$? -ne 0 ] && {
	# remove the ibm.ps.client.bind authorization
	(/usr/sbin/rmauth ibm.ps.client.bind
	 /usr/sbin/rmauth ibm.ps.client
	 /usr/sbin/rmauth ibm.ps
	 /usr/sbin/rmauth ibm) > /dev/null 2>&1
    }

    # NB: safe even if it does not exist
    /usr/sbin/rmrole CdatClient > /dev/null 2>&1

    # set entries in the kernel security tables
    /usr/sbin/setkst -q -t role,auth > /dev/null 2>&1
}
exit 0 # discard errors
EOF
;

    my @lines = cdat::remote_cmd('root', $name, $cmd,
	$cdat::REMOTE_ASKUSER | $cdat::REMOTE_PROBE);
    DEBUG(1, @lines);
    return $?;
}

######################################################################
# Function:	vios_install
# Purpose:	Initialize access to a VIOS.
# Tasks:	Initialize access to a VIOS.
# Input:	hostname, user to create, password
# Output:	0 on success, remote command exit status on failure
######################################################################
sub vios_install
{
    my ($name, $user, $passwd) = @_;

    my $cmd = <<EOF
ioscli oem_setup_env << EOT
# check if the CdatClient RBAC role already exists
/usr/sbin/lsrole CdatClient > /dev/null 2>&1
[ \\\$? -ne 0 ] && {
    # create our CDAT role
    /usr/bin/mkrole authorizations=aix.system.stat,aix.ras.trace CdatClient
    [ \\\$? -ne 0 ] && exit 2
    # set entries in the kernel security tables
    /usr/sbin/setkst -q -t role,auth > /dev/null 2>&1
}
# check if the user already exists
/usr/sbin/lsuser "$user" > /dev/null 2>&1
[ \\\$? -ne 0 ] && {
    # create the user with the CdatClient RBAC role
    /usr/bin/mkuser roles=CdatClient default_roles=CdatClient "$user" > /dev/null 2>&1
    [ \\\$? -ne 0 ] && exit 3
    # set the initial password
    echo "$user:$passwd" | /usr/bin/chpasswd -f NOCHECK
    [ \\\$? -ne 0 ] && exit 4

    # create the .cdat file indicating that we created this user ourselves
    /usr/bin/su $user "-c touch /home/$user/.cdat" > /dev/null 2>&1
}
[ ! -d /home/$user/.ssh ] && {
    # create the .ssh directory
    /usr/bin/su $user "-c mkdir -p /home/$user/.ssh" > /dev/null 2>&1
    [ \\\$? -ne 0 ] && exit 5
}
# always set correct permissions for the .ssh directory
/usr/bin/su $user "-c chmod 700 /home/$user/.ssh" > /dev/null 2>&1
[ ! -f /home/$user/.ssh/authorized_keys2 ] && {
    # create an empty authorized_keys2 file
    /usr/bin/su $user "-c touch /home/$user/.ssh/authorized_keys2" > /dev/null 2>&1
    [ \\\$? -ne 0 ] && exit 6
}
# always set correct permissions for the authorized_keys2 file
/usr/bin/su $user "-c chmod 600 /home/$user/.ssh/authorized_keys2" > /dev/null 2>&1
[ ! -z "$pubkey" ] && {
    # check if our SSH public key is already installed
    /usr/bin/grep -q "$pubkey" /home/$user/.ssh/authorized_keys2
    [ \\\$? -ne 0 ] && {
        # install our SSH public key
        echo "$pubkey" >> /home/$user/.ssh/authorized_keys2
        [ \\\$? -ne 0 ] && exit 7
    }
}
exit 0
EOT
EOF
;

    my @lines = cdat::remote_cmd('padmin', $name, $cmd,
	$cdat::REMOTE_ASKUSER | $cdat::REMOTE_PROBE);
    DEBUG(1, @lines);
    return $?;
}

######################################################################
# Function:	vios_uninstall
# Purpose:	Remove access to a VIOS.
# Tasks:	Remove access to a VIOS.
# Input:	hostname, user to remove
# Output:	0 on success, remote command exit status on failure
######################################################################
sub vios_uninstall
{
    my ($name, $user) = @_;

    my $cmd = <<EOF
ioscli oem_setup_env << EOT
if [ "$user" != "padmin" -a -f /home/$user/.cdat ]; then
    # remove the user and its home directory
    /usr/sbin/userdel -r $user > /dev/null 2>&1
elif [ ! -z "$pubkey" -a -f /home/$user/.ssh/authorized_keys2 ]; then
    /usr/bin/su $user "-c touch /home/$user/.ssh/authorized_keys2.bak"
    [ \\\$? -eq 0 ] && {
        chmod 600 /home/$user/.ssh/authorized_keys2.bak
        [ \\\$? -eq 0 ] && {
            # remove our SSH public key
            /usr/bin/grep -v "$pubkey" /home/$user/.ssh/authorized_keys2 > /home/$user/.ssh/authorized_keys2.bak
            cat /home/$user/.ssh/authorized_keys2.bak > /home/$user/.ssh/authorized_keys2
            rm -f /home/$user/.ssh/authorized_keys2.bak
	}
    }
fi
# remove the CdatClient RBAC role if it is no longer used
/usr/sbin/lsuser ALL | grep -wq CdatClient
[ \\\$? -ne 0 ] && {
    # NB: safe even if it does not exist
    /usr/sbin/rmrole CdatClient > /dev/null 2>&1

    # set entries in the kernel security tables
    /usr/sbin/setkst -q -t role,auth > /dev/null 2>&1
}

exit 0 # discard errors
EOT
EOF
;

    my @lines = cdat::remote_cmd('padmin', $name, $cmd,
	$cdat::REMOTE_ASKUSER | $cdat::REMOTE_PROBE);
    DEBUG(1, @lines);
    return $?;
}


######################################################################
# Function:	hmc_install
# Purpose:	Initialize access to an HMC.
# Tasks:	Initialize access to an HMC.
# Input:	hostname, user to create, password
# Output:	0 on success, remote command exit status on failure
######################################################################
sub hmc_install
{
    my ($name, $user, $passwd) = @_;

    my $cmd = <<EOF
lshmcusr --filter "names=$user" -F name | grep -wq "$user"
[ \$? -ne 0 ] && {
    mkhmcusr -u "$user" -d "Cluster Data Aggregation Tool" -a hmcsuperadmin --passwd "$passwd"
    [ \$? -ne 0 ] && exit 1
}
[ ! -z "$pubkey" ] && {
    # remove our SSH public key if it already exists
    mkauthkeys -u "$user" -r "$pubkey"
    # install our SSH public key
    mkauthkeys -u "$user" -a "$pubkey"
    [ \$? -ne 0 ] && exit 2
}
exit 0
EOF
;

    my @lines = cdat::remote_cmd('hscroot', $name, $cmd,
	$cdat::REMOTE_ASKUSER | $cdat::REMOTE_PROBE);
    DEBUG(1, @lines);
    return $?;
}


######################################################################
# Function:	hmc_uninstall
# Purpose:	Remove access to an HMC.
# Tasks:	Remove access to an HMC.
# Input:	hostname, user to remove
# Output:	0 on success, remote command exit status on failure
######################################################################
sub hmc_uninstall
{
    my ($name, $user) = @_;

    my $cmd = <<EOF
lshmcusr --filter "names=$user" -F name | grep -wq "$user"
[ \$? -eq 0 ] && {
    [ ! -z "$pubkey" ] && {
        # remove our SSH public key
        mkauthkeys -u "$user" -r "$pubkey"
    }
    # remove the user
    rmhmcusr -u "$user"
}
exit 0
EOF
;

    my @lines = cdat::remote_cmd('hscroot', $name, $cmd,
	$cdat::REMOTE_ASKUSER | $cdat::REMOTE_PROBE);
    DEBUG(1, @lines);
    return $?;
}


######################################################################
# Function:	access
# Purpose:	Init/Deinit CDAT on a remote node.
# Tasks:	Init/Deinit CDAT on a remote node (create user, RBAC
#		role, exchange SSH keys etc...)
# Input:	Node type, name, remote user to be created, password.
# Output:	0 on success, 1 on failure
######################################################################
sub access
{
    my ($type, $name, $user, $passwd) = @_;
    my $rc;

    if ($type eq 'HMC') {
        if (defined($delete)) {
	    $rc = hmc_uninstall($name, $user);
	} else {
	    $rc = hmc_install($name, $user, $passwd);
	}
    } elsif ($type eq 'VIOS') {
        if (defined($delete)) {
	    $rc = vios_uninstall($name, $user);
	} else {
	    $rc = vios_install($name, $user, $passwd);
	}
    } else {
        if (defined($delete)) {
	    $rc = lpar_uninstall($name, $user);
	} else {
	    $rc = lpar_install($name, $user, $passwd);
	}
    }
    if ($rc != 0) {
        if (defined($delete)) {
            printf(STDERR catgets(MSG_CANNOT_REMOVE_ACCESS,
	        "Cannot remove access to '%s' on host '%s' (error %d).\n"),
		$user, $name, $rc);
        } else {
            printf(STDERR catgets(MSG_CANNOT_INIT_ACCESS,
	        "Cannot initialize access to '%s' on host '%s' (error %d).\n"),
		$user, $name, $rc);
        }
	return 1;
    }
    printf(catgets(MSG_DONE, "Done.\n"));
    return 0;
}

######################################################################
# Function:	main
# Purpose:	Entry point of the access subcommand.
# Tasks:	Parse command line and publish SSH public keys on
#		remote nodes.
# Input:	None
# Output:	None
######################################################################
sub main
{
    my ($rc, $user, $passwd, $force);
    my (@filenames, @rawnodes);
    my %nodes;
    my $has_ssh;

    # Parse command line options
    Getopt::Long::Configure('bundling', 'no_ignore_case');
    $rc = GetOptions(
	'h'   => \&usage,
	'd'   => \$delete,
	'F'   => \$force,
	'n=s' => \@rawnodes,
	'u=s' => \$user,
	'f=s' => \@filenames,
	);
    if (!$rc || @ARGV != 0) {
	usage();
    }

    # make sure we have -n or -f, but not both
    if (!((@filenames != 0) ^ (@rawnodes != 0))) {
        usage();
    }

    cdat::switch_user();

    if (@filenames != 0) {
	%nodes = cdat::read_nodes_from_files(@filenames);
    } else {
        %nodes = cdat::read_nodes_from_array(@rawnodes);
    }
    if (!%nodes) {
        printf(STDERR catgets(MSG_NO_NODES_TO_CONNECT,
	    "No nodes to connect to.\n"));
	exit(3);
    }

    if (!defined($user)) {
        # no -u specified, use default collect user
	$user = $USER;
    }

    $has_ssh = -x '/usr/bin/ssh';
    if ($has_ssh) {
        if (open(FILE, "$ENV{HOME}/.ssh/id_rsa.pub")) {
            $pubkey = <FILE>;
	    chomp($pubkey);
            close(FILE);
        }
    }

    my %hosts;
    cdat::read_host_db(\%hosts);

    my $status = 0;
    while (my ($name, $node) = each(%nodes)) {
        # Skip node types that we don't manage (PSCALE)
	my $type = $node->{type};
	if ($type ne 'HMC' && $type ne 'VIOS' && $type ne 'LPAR') {
	    printf(catgets(MSG_ACCESS_IGNORE_TYPE,
	        "Ignoring node type '%s'.\n"), $type);
	    next;
	}

	my $login = $node->{user};
	# if no user@ specified, use -u (or default)
	$login = $user if (!defined($login));

	if (!defined($force)) {
	    # -F not specified, skeep nodes already (de)initialized
	    if (!defined($delete)) {
	        if (defined($hosts{$name}) &&
		    defined($hosts{$name}{users}{$login})) {
	            # User@Host is already in hosts db, use force to re-create
		    printf(catgets(MSG_ACCESS_ALREADY_IN_DB,
		    	"%s@%s is already in hosts database, use -F to force.\n"),
		        $login, $name);
	            next;
	        }
	    } else {
	        if (!defined($hosts{$name}) ||
		    !defined($hosts{$name}{users}{$login})) {
	            # User@Host is not in hosts db, use force to delete anyway
		    printf(catgets(MSG_ACCESS_NOT_IN_DB,
		        "%s@%s is not in hosts database, use -F to force.\n"),
		        $login, $name);
	            next;
	        }
	    }
	}

	if (!defined($passwd) && !defined($delete)) {
            printf(catgets(MSG_USER_PASSWD,
        	"The collect user will be created with the same password on all nodes.\n"));

            # Ask for a password and a confirmation of the password
            my $prompt1 = catgets(MSG_PLEASE_ENTER_PASSWORD,
        	"Please enter a password for the collect user: ");
            my $prompt2 = catgets(MSG_REENTER_PASSWORD,
        	"Re-enter the collect user password: ");
            $passwd = cdat::define_password($prompt1, $prompt2);
            if (!defined($passwd)) {
		printf(catgets(MSG_PASSWORD_MISMATCH, "Password mismatch.\n"));
		exit(1);
            } elsif (length($passwd) < 7) {
		# this is required for HMCs
		printf(catgets(MSG_PASSWORD_INVALID,
	    	    "Password cannot be less than 7 characters.\n"));
		exit(1);
	    }
	}

	if (defined($delete)) {
	    printf(catgets(MSG_REMOVE_ACCESS,
		"Removing access to '%s' on host '%s'...\n"),
		$login, $name);
	} else {
	    printf(catgets(MSG_INIT_ACCESS,
	        "Initializing access to '%s' on host '%s'...\n"),
		$login, $name);
	}
	if ($has_ssh) {
	    printf(catgets(MSG_ACCESS_TRYING, "Trying '%s'..."), "ssh");
	    if (cdat::check_remote_protocol($name, "ssh")) {
	        printf(catgets(MSG_FOUND, "found\n"));
		$status = access($type, $name, $login, $passwd);
		next if ($status != 0);
		if (!defined($delete)) {
		    $hosts{$name}{protocol} = "ssh";
		    # NB: we do not store passwords for SSH
		    $hosts{$name}{users}{$login} = "";
		} elsif (defined($hosts{$name})) {
		    delete($hosts{$name}{users}{$login});
		}
		next;
	    }
	    printf(catgets(MSG_NOT_FOUND, "not found\n"));
	}
	printf(catgets(MSG_ACCESS_TRYING, "Trying '%s'..."), "exec");
	if (cdat::check_remote_protocol($name, "exec")) {
	    printf(catgets(MSG_FOUND, "found\n"));
	    $status = access($type, $name, $login, $passwd);
	    next if ($status != 0);
	    if (!defined($delete)) {
		$hosts{$name}{protocol} = "exec";
		$hosts{$name}{users}{$login} = $passwd;
	    } elsif (defined($hosts{$name})) {
		delete($hosts{$name}{users}{$login});
	    }
	    next;
	}
	printf(catgets(MSG_NOT_FOUND, "not found\n"));
	printf(catgets(MSG_ACCESS_TRYING, "Trying '%s'..."), "telnet");
	if (cdat::check_remote_protocol($name, "telnet")) {
	    printf(catgets(MSG_FOUND, "found\n"));
	    $status = access($type, $name, $login, $passwd);
	    next if ($status != 0);
	    if (!defined($delete)) {
	        $hosts{$name}{protocol} = "telnet";
	        $hosts{$name}{users}{$login} = $passwd;
		$hosts{$name}{login_pattern} = $ENV{EXPECT_PATTERN1};
		$hosts{$name}{password_pattern} = $ENV{EXPECT_PATTERN2};
	    } elsif (defined($hosts{$name})) {
		delete($hosts{$name}{users}{$login});
	    }
	    next;
	}
	printf(catgets(MSG_NOT_FOUND, "not found\n"));
	printf(catgets(MSG_CANNOT_CONNECT_TO,
	    "Cannot connect to %s.\n"), $name);
    }

    cdat::write_host_db(%hosts);
    exit($status);
}

main;
