#!/usr/bin/perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#(C)IBM Corp

#

#-----------------------------------------------------------------------------

=head1   aixremoteshell

   This sets up the remote shell for root on the AIX node,such that root can
   login with no password. The default is /bin/rsh and
   /bin/rcp but can be overriden by setting the useSSHonAIX attribute in the
   site table to yes, in which case we will use ssh/scp.
   The aixremoteshell postscripts will be called from remoteshell postscript
   when on an AIX node.  It will input a -d ( for do)  flag, to indicate to run
   If the flag is not input, the routine will do nothing.  This is to have only
   remoteshell in the postscripts table whether supporting AIX or Linux,
   and better support mixed clusters.

=cut

#-----------------------------------------------------------------------------

#
#  If USESSHONAIX does not exist or is no or 0 then
#     setup .rhosts on the node
#  else (ssh)
#     setup the ssh keys on the node
#  end

# MAIN
use strict;
use IO::Socket;
my $useSocketSSL = eval { require IO::Socket::SSL; };
if ($useSocketSSL) {
    require IO::Socket::SSL;
}
use Getopt::Long;
my $rc = 0;

# Override from site table
my $usesshonaix           = $ENV{'USESSHONAIX'};
my $master                = $ENV{'MASTER'};
my $node                  = $ENV{'NODE'};
my $nodetype              = $ENV{'NTYPE'};
my $enablesshbetweennodes = $ENV{'ENABLESSHBETWEENNODES'};
my $msg;
my $home;
my $cmd;
my $runscript;
my $username = "root";
Getopt::Long::Configure("bundling");
Getopt::Long::Configure("pass_through");
GetOptions(
    'd|do' => \$runscript
);

#  the postscript only runs if called with -d flag from remoteshell
unless ($runscript) {
    my $msg = "aixremoteshell called without -d flag, do nothing.";
    `logger -t xcat -p local4.err $msg`;
    exit 0;
}

my @root = split ':', (`/bin/grep ^$username /etc/passwd 2>&1`);
$home = $root[5];

#  root home directory must be root system
$rc = &runcmd("chown root $home");
if ($rc != 0)
{
    my $msg = "It fails to chown root $home";
    `logger -t xcat -p local4.err $msg`;
    exit 1;
}
$rc = &runcmd("chgrp system $home");
if ($rc != 0)
{
    my $msg = "It fails to chgrp system $home";
    `logger -t xcat  -p local4.err $msg`;
    exit 1;
}

$usesshonaix =~ tr/a-z/A-Z/;    # convert to upper
if ((!defined($usesshonaix)) || ($usesshonaix eq "0") || ($usesshonaix eq "NO"))
{                               # setting up rsh
                                # setup .rhosts if not already setup
    $cmd = "/bin/grep \"^$master root\" /.rhosts";
    `$cmd 2>&1`;
    my $rc = $? >> 8;
    if ($rc)
    {                           # if not found, then add entry in .rhosts
        &runcmd("/bin/echo $master root >> /.rhosts");
        chmod 0600, "/.rhosts";
    }

}
else
{
    # setting up ssh
    if (&setupSSH != 0) {
        my $msg = "Failed to setup ssh on $node.\n";
        `logger -t xcat  -p local4.err  $msg`;

        #	print $msg;
        exit 0;
    }
}

exit 0;

#
# Subroutines
#

sub setupSSH
{
    my $sshdconfig = "/etc/ssh/sshd_config";
    my $sshconfig  = "/etc/ssh/ssh_config";

    if (-e $sshdconfig)
    {    # ssh installed
        my $tmp = "$sshdconfig.ORIG";
        if (!(-e "$sshdconfig.ORIG"))
        {
            &runcmd("cp $sshdconfig $sshdconfig.ORIG");
        }
        &runcmd("echo \"KeyRegenerationInterval 0\" >>$sshdconfig");
        &runcmd("echo \"X11Forwarding yes\" >>$sshdconfig");
        &runcmd("echo \"MaxStartups 1024\" >>$sshdconfig");
        &runcmd("echo \"ListenAddress ::\" >>$sshdconfig");
        &runcmd("echo \"ListenAddress 0.0.0.0\" >>$sshdconfig");

        #&runcmd("echo \"PasswordAuthentication no\" >>$sshdconfig");
        if (!(-e "$sshconfig.ORIG"))
        {
            &runcmd("cp $sshconfig $sshconfig.ORIG");
        }
        &runcmd("echo \"StrictHostKeyChecking no\" >>$sshconfig");
    }
    else
    {    # ssh not installed
        my $msg = "Failed to setup ssh on $node, ssh not installed. \n";
        `logger -t xcat -p local4.err  $msg`;
        exit 0;
    }

    if (-e "/xcatpost/_ssh")
    {    # ssh public key available
        $rc = &runcmd("mkdir -p /.ssh");
        if ($rc == 0)
        {
            $rc = &runcmd("cp -fp /xcatpost/_ssh/* /.ssh");
            if ($rc == 0)
            {
                $rc = &runcmd("chmod 0700 /.ssh");
                $rc = &runcmd("chmod 0600 /.ssh/*");
            }
        }
    }
    else
    {    # ssh keys not available
        my $msg = "Failed to setup ssh on $node, ssh keys not available. \n";
        `logger -t xcat  -p local4.err $msg`;
        exit 0;
    }

    #  get the name of my service node/NIM master from /etc/xcatinfo
    #  ! use value of MASTER env variable instead
    $::servnode = $master;

    my $response = &getresponse("ssh_dsa_hostkey");
    if (defined($response)) {
        my $fd;
        my $filename = "/etc/ssh/ssh_host_dsa_key";
        &runcmd("mkdir -p /etc/ssh");
        open($fd, '>', $filename);
        print $fd $response;
        close($fd);

        # set the permissions
        my $cmd = "chmod 600 $filename > /dev/null 2>&1";
        &runcmd($cmd);
    }
    else {
        $msg = "aixremoteshell: Could not get ssh_host_dsa_key file.\n";
        `logger -t xcat  -p local4.err  $msg`;
    }

    my $response = &getresponse("ssh_rsa_hostkey");
    if (defined($response)) {
        my $fd;
        my $filename = "/etc/ssh/ssh_host_rsa_key";
        &runcmd("mkdir -p /etc/ssh");
        open($fd, '>', $filename);
        print $fd $response;
        close($fd);

        # set the permissions
        my $cmd = "chmod 600 $filename > /dev/null 2>&1";
        &runcmd($cmd);
    }
    else {
        $msg = "aixremoteshell: Could not get ssh_host_rsa_key file.\n";
        `logger -t xcat  -p local4.err $msg`;
    }

    # is there is a ecdsa host key on the node, then get the one from the MN/SN
    my $filename = "/etc/ssh/ssh_host_ecdsa_key";
    if (-e $filename) {
        my $response = &getresponse("ssh_ecdsa_hostkey");
        if (defined($response)) {
            my $fd;
            &runcmd("mkdir -p /etc/ssh");
            open($fd, '>', $filename);
            print $fd $response;
            close($fd);

            # set the permissions
            my $cmd = "chmod 600 $filename > /dev/null 2>&1";
            &runcmd($cmd);
        }
        else {
            $msg = "aixremoteshell: Could not get ssh_host_ecdsa_key file.\n";
            `logger -t xcat  -p local4.err $msg`;
        }
    }
    if ($nodetype eq "service") {
        &runcmd("mkdir -p /etc/xcat/hostkeys; cp /etc/ssh/ssh* /etc/xcat/hostkeys/. > /dev/null 2>&1");
    }

    # Decide whether to enable passwordless ssh between the nodes
    if ($enablesshbetweennodes eq "YES") {
        my $response = &getresponse("ssh_root_key");
        if (defined($response)) {
            my $fd;
            my $filename = "/.ssh/id_rsa";
            &runcmd("mkdir -p /.ssh");
            open($fd, '>', $filename);
            print $fd $response;
            close($fd);

            # set the permissions
            my $cmd = "chmod 600 $filename > /dev/null 2>&1";
            &runcmd($cmd);

            if (-f "/.ssh/id_rsa") {
                &runcmd("ssh-keygen -y -f /.ssh/id_rsa > /.ssh/id_rsa.pub");
            }
        }
        else {
            $msg = "aixremoteshell: Could not get id_rsa file.\n";
            `logger -t xcat  -p local4.err  $msg`;
        }
    }

    return 0;
}

#####################################################
#
#  getresponse
#       Request info from xcatd on the management node
#
#	- uses SSL socket on port 3001 to connect to MN xcatd
#		to make the request for info
#
#####################################################
sub getresponse
{
    my ($req) = @_;

    my $port = "3001";

    # open listener connection to wait for check from management node
    my $lpid = &openlistener();

    # open a socket to request credentials
    my $sock = IO::Socket::SSL->new(
        PeerAddr => $::servnode,
        PeerPort => $port,
        Proto    => 'tcp',
    );

    unless ($sock) {
        my $msg = "aixremoteshell: Cannot connect to host \'$::servnode\'\n";
        `logger -t xcat  -p local4.err  $msg`;

        #print $msg;
        kill 2, $lpid;
        return undef;
    }

    # request must be in XML format
    print $sock "<xcatrequest>\n";
    print $sock "   <command>getcredentials</command>\n";
    print $sock "   <arg>$req</arg>\n";
    print $sock "   <callback_port>300</callback_port>\n";
    print $sock "</xcatrequest>\n";

    #TODO - do we have to try again after waiting for a bit????
    my $response = '';
    my $line;
    while (defined($line = <$sock>)) {

        # skip xml tags
        next if ($line =~ /^\s*</);

        # once we get to serverdone we have the whole response
        if ($line =~ m/<\/serverdone>/) {
          last:
        }
        $response .= $line;
    }

    close($sock);

    kill 2, $lpid;
    if ($response) {
        return $response;
    }
    return undef;
}

#####################################################
#
#  openlistener
#   - fork a child process to respond to a check from the MN
#
#####################################################
sub openlistener
{
    my $node = $ENV{'NODE'};

    # fork a child process to open a socket to listen for communication
    #	from the server
    my $pid = fork;
    unless (defined $pid) {

        # fork failed
        $msg = "aixremoteshell: Could not fork process.\n";
        `logger -t xcat  -p local4.err  $msg`;
        print $msg;
        return undef;
    }

    if ($pid != 0) {

        # This is the parent process, just return
        return $pid;
    }

    my $listener = IO::Socket::INET->new(
        LocalPort => '300',
        Proto     => 'tcp',
        Listen    => '64',
        Reuse     => 1
    );

    unless ($listener) {
        my $msg = "aixremoteshell: Cannot open socket on \'$node\'\n";
        `logger -t xcat  -p local4.err $msg`;
        print $msg;
        exit 1;
    }

    #	xcatd sends a quick req to see if we are really asking
    #  	for info - this listener checks for the req and says ok
    my $client;
    while ($client = $listener->accept()) {

        # $client is the new connection
        my $text = <$client>;

        #  see if we got "CREDOKBYYOU?"
        if ($text =~ /CREDOKBYYOU?/) {
            print $client "CREDOKBYME";
            close($client);
            close($listener);
            exit 0;
        }
        close($client);
    }
    close($client);
    close($listener);
    exit 0;
}

#
# run the command
#
sub runcmd
{
    my ($cmd) = @_;
    my $rc = 0;
    $cmd .= ' 2>&1';
    $::outref = `$cmd`;
    if ($?)
    {
        $rc = $? >> 8;
        if ($rc > 0)
        {
            my $msg = "$cmd returned rc=$rc @$::outref\n";
            `logger -t xcat  -p local4.info $msg`;

            #			print $msg;
        }
    }
    return 0;
}

