#!/usr/perl5/bin/perl -w
#
# Copyright 2001-2003 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#ident	"@(#)network_cmd.pl 1.7	03/05/22 SMI"
#
# Perform a IPMP task on the local node
#

require 5.005;
use strict;
use lib '/opt/SUNWscvw/lib/perl';
use Cluster::Cgi;
use Cluster::Network;
use Sun::Solaris::Utils qw(gettext);
use Cluster::Common;

#
# The following subroutine takes a incoming string and operate the following :
# - If the passed string is a valid host IP address in dot notation form, 
#   return it as result.
# - If the passed string is not a valid host IP address in dot notation form, 
#   it assumes that it is a hostname and try to retrieve the IP addresses
#   and returned the first encountered one.
# - Else return an empty string, signaling error.
# 
# Note: Validity of the passed string is just checked around the form of that
#       string, e.g. it is declared as valid if it is a succession of four
#       non nul numeral separated with dots.
#
sub translate_to_ip {

  my $arg = shift;

  if(!$arg) {
    return;
  }

  my $result = "";

  if ($arg =~ /(\d+[.]){3}\d+/) {
    # Passed argument is already an IP address in dot notation form
    $result = $arg;
  } else {
    # Interpret passed string as a hostname, try to retrieve the associated
    # IP address. The first found one will be retained.
    my ($addr, @junk) = Cluster::Common::get_ip_addresses(undef, $arg);

    # Following code section is used to reject address with 0 as path.
    if($addr) {
      my @path = split(/./,$addr);
      my $ok="true";
      for(my $i=0; ($i<=$#path) and $ok; $i++) {
	if($path[$i]=="0") {
	  $ok="";
	}
      }
      if($ok) {
	$result = $addr;
      }
    }
  }

  return $result;
}

# Create a cgi object
my $q = new Cluster::Cgi;

# Create a install object
my $network = new Cluster::Network;

# Print the header
print $q->header();

# Get the operation
my ($opline) = $q->param("cmd");
# Keep only valid characters
$opline =~ s/[^a-zA-Z0-9\.\-_ ]//g;

my $cmd;
my $precmd;
my $hostnamecmd;
my $output;

if ($opline =~ "^add") {
    # Add an adapter
    my ($op, $group, $adp, $taddr, $standby, $addr) =
	split(/ /, $opline);

    if ($adp eq "") {
	print gettext("Adapter name missing"),"\n";
	print "error\n";
	return;
    }
    if ($taddr eq "") {
	print gettext("Test address missing"),"\n";
	print "error\n";
	return;
    }
    if ($group eq "") {
	print gettext("Group name missing"),"\n";
	print "error\n";
	return;
    }

    if ($standby eq "on") {
	$standby = "standby ";
    } else {
	$standby = "-standby ";
    }

    my $file = "/etc/hostname.$adp";

    # Retrieve physical address bind to adapter from the configuration file
    # Making the assumption that the configuration is correct, the physical
    # address is the first word encountered in the file.

    my $physicalAddr = "";  # The physical address bind to adapter

    my ($oldhostnamecmd) = "";
    if (-e $file) {
     $oldhostnamecmd = `/bin/pfexec /usr/bin/cat $file`;
      if ($oldhostnamecmd =~ /^(\S+)/) {
	my @splitcontent = split(/\s/,$oldhostnamecmd);
	$physicalAddr = $splitcontent[0];
      }
    }

    # Translate the dotted form of the physical address
    my $physicalIP;
    if($physicalAddr) {
      $physicalIP = translate_to_ip($physicalAddr);
    }

    # The set of logical addresses to handle is the test address and the 
    # optionale base logical address.
    my @logicalAddrs; # The logical address bind to adapter

    push(@logicalAddrs,$taddr);

    if($addr) {
      push(@logicalAddrs,$addr);
    }

    # Retrieve current runtime network configuration 
    my $ifconfigOutput = `/bin/pfexec /sbin/ifconfig -a 2>&1`;
    my @cfgLines = split(/\n/,$ifconfigOutput);
    my @itfs; # a list containing the runtime network configuration
    for(my $i=0; $i<=$#cfgLines; $i++) {
      if(grep(/^\w\S*:/,$cfgLines[$i])){	
        push(@itfs,$cfgLines[$i]);
      } else {        
        $itfs[$#itfs] .= $cfgLines[$i];
      }
    }	

    # Translate logical address to IP addresses and check that these logical
    # adresses are not identical to the physical one (the one present in
    # the configuration file and the one currently binded to the adapter
    my @logicalIPs;  # The logical IP address bind to adapter
    my $testIP;

    foreach my $logicalAddr (@logicalAddrs) {
      
      my $ipAddr = translate_to_ip($logicalAddr);

      if(!$ipAddr) {
	printf(gettext("Unable to resolve address : '%s'\n"), "$logicalAddr");
	print "error\n";
	return;
      }
      
      if($logicalAddr eq $taddr) {
	$testIP=$ipAddr;
      }

      # Check that the address is not equivalent to the physical address 
      # present in the configuration file.
      if($physicalIP and ($ipAddr eq $physicalIP)) {
	printf(gettext("Address %s is configured in the file %s as the physical address for the network adapter %s and therefor cannot be used as logical address.\n"), $logicalAddr, $file, $adp);
	print "error\n";
	return;
      }

      # Lookup in the runtime configuration if the logical address is 
      # the physical address of the network adapter.

      for(my $i=0; $i<=$#itfs; $i++) {
	# Retrieve the name of the interface 
	my ($itfname,@junk) = split(/\s/,$itfs[$i]);
	$itfname =~ s/:$//;

	if($itfname eq $adp) {
	  if(grep(/$ipAddr/,$itfs[$i])) {
	    printf(gettext("Address %s is currently bind on the network adapter %s as physical address and therefor cannot be used.\n"), $logicalAddr, $adp);
	    print "error\n";
	    return;
	  }

	  # If we don't have any physical address in the configuration file
	  # but if there is one at this level. Take this one as the 
	  # physical address to keep.
	  my ($junk,$yep) = split(/inet /,$itfs[$i]);
	  my ($inet,@junk) = split(/\s/,$yep);
	  $physicalAddr = $inet;
	  $physicalIP = $inet;
	}
      }

      # Only push retained the IP address if it is not already in the 
      # logical IP set.
      if(! (join(" ",@logicalIPs) =~ "$ipAddr")) {
	push(@logicalIPs, $ipAddr);
      }
    }    
    
    # Runtime reconfiguration phase
    my (@lines); # lines to put in /etc/hostname.<adapt> file

    # Physical address configuration 
    my ($plumbflag) = "plumb ";

    # If at this point we still have no physical address, used the first 
    # logical address as the physical address for the interface.
    my $cmd;
    if(!$physicalAddr) {
      $physicalAddr = shift(@logicalIPs);
      $physicalIP = $physicalAddr;
    }
     
    $cmd = $physicalAddr . " netmask + broadcast +";
    if($physicalIP eq $testIP) {
      $cmd .= " deprecated -failover";
    }
    
    # Add standby flag
    $cmd .= " $standby";

    # Add group flag
    $cmd .= " group $group";
    # Add up flag
    $cmd .= " up";
    push(@lines,$cmd);
    
    $cmd = "ifconfig $adp " . $plumbflag . $cmd;
    print "cmd=$cmd\n";
    
    # Execute the command
    $output = `/bin/pfexec /sbin/$cmd 2>&1`;
    print $output;
    if ($? != 0) {
      print "error\n";
      return;
    }

    # Add the other logical address

    # For each logical address to treat, distinguish those that already
    # exist from those that don't exist.
    # For the first one, a 'removeif' command will be first issue 
    # before an 'addif' command to recreate it.

    my ($removeifflag) = "";

    my $addifcmd="";
    $cmd="";

    foreach my $ipAddr (@logicalIPs) {

      # Is there a interface binded with the current IP address
      # If it is binded on the another adpater card, raise an error.
      for(my $i=0; $i<=$#itfs and !$removeifflag; $i++) {

	if(grep(/$ipAddr/,$itfs[$i])) {

	  my @junk = split(/\s/,$itfs[$i]);
	  my $itfName = $junk[0];
	  $itfName =~ s/:$//;

	  # If the IP address has been found on a different network adapter
	  # raise an error
	  if(! grep(/$adp/,$itfName)) {
	    printf(gettext("Address %s is already bind on the network adapter %s and therefor cannot be bind to the adapter %s.\n"),$ipAddr,$itfName,$adp);
	    print("error\n");
	    return;
	  }

	  # Else fill the removeif flag
	  $removeifflag = " removeif $ipAddr ";
	}
      }
      
      $addifcmd .= " addif " . $ipAddr;

      if ($ipAddr eq $testIP) {
	# Add flags for test addr
	$addifcmd .= " deprecated -failover $standby";	
      }

      # Add up flag
      $addifcmd .= " up";
      push @lines, $addifcmd;
    }
      
    if($addifcmd) {
      # Now execute the command
      $cmd = "ifconfig $adp $cmd".$removeifflag.$addifcmd;
      print "cmd=$cmd\n";
      
      $output = `/bin/pfexec /sbin/$cmd 2>&1`;
      print $output;
      if ($? != 0) {
	print "error\n";
	return;
      }
    }

    # make sure that adapter is attached :
    $cmd = "if_mpadm -r $adp";
    print "cmd=$cmd\n";
    $output = `/bin/pfexec /sbin/$cmd 2>&1`;
    
    # Create the new hostname file
    if (! open(OUT, "| /bin/pfexec /bin/tee $file.new > /dev/null")) {
      printf(gettext("Couldn't open file %s")."\n", "$file.new");
      print "error\n";
      return;
    }
    print OUT join(" \\\n ", @lines), "\n";
    if (! close(OUT)) {
      printf(gettext("Couldn't close  file %s")."\n", "$file.new");
      print "error\n";
      return;
    }
    
    if (-e $file) {
	# Rename the old hostname file
	$output = `/bin/pfexec /bin/mv $file $file.old 2>&1`;
	if ($? != 0) {
	    printf(gettext("Couldn't rename file %s")."\n", "$file");
	    print "error\n";
	    return;
	}
    }

    # Replace the existing hostname file with the new one
    $output = `/bin/pfexec /bin/mv $file.new $file 2>&1`;
    if ($? != 0) {
	printf(gettext("Couldn't rename file %s")."\n", "$file.new");
	print "error\n";
	return;
    }
  } elsif ($opline =~ "^remove") {
    # Remove an adapter
    my ($op, $adp, $group, $unplumb) = split(/ /, $opline);

    if ($adp eq "") {
	print gettext("Adapter name missing"),"\n";
	print "error\n";
	return;
    }

    if ($opline !~ /removelast/) {
	# If not removing last adapter, do if_mpadm command

	$cmd = "if_mpadm -d $adp";
	print "cmd=$cmd\n";
	$output = `/bin/pfexec /usr/sbin/$cmd 2>&1`;
	print $output;
	if ($? != 0) {
	    print "if_mpadm error";
	    return;
	}
    }

    # Perform the ifconfig task

    if ($unplumb eq "true") {
	$cmd = "ifconfig $adp group \"\" unplumb";
    } else {
	$cmd = "ifconfig $adp group \"\"";
    }
    print "cmd=$cmd\n";

    $output = `/bin/pfexec /sbin/$cmd 2>&1`;
    print $output;
    if ($? != 0) {
	print "error\n";
	return;
    }

    # Update the hostname file

    my $file = "/etc/hostname.$adp";

    if (-e $file) {

      # Create new hostname file : if unmplumb is not requested
      if ($unplumb eq "false") {

	my ($hostnamecmd, $newcmd);
	$hostnamecmd = `cat $file`;
	($newcmd = $hostnamecmd) =~ s/group[\\\s]+\S+//gs;
	
	# Reconfiguration is identical, don't need to update the file
	if ($newcmd eq $hostnamecmd) {
	  return;
	}
      
	if (! open(OUT, "| /bin/pfexec /bin/tee $file.new > /dev/null")) {
	  printf(gettext("Couldn't open file %s")."\n", "$file.new");
	  print "error\n";
	  return;
	}
	print OUT $newcmd;
	if (! close(OUT)) {
	  printf(gettext("Couldn't open file %s")."\n", "$file.new");
	  print "error\n";
	  return;
	}
      }

      # Rename the old hostname file
      $output = `/bin/pfexec /bin/mv $file $file.old 2>&1`;
      if ($? != 0) {
	printf(gettext("Couldn't rename file %s")."\n", "$file");
	print "error\n";
	return;
      }

      # Replace the existing hostname file with the new one; only if a new
      # file has been created.
      if (-e "$file.new") {
	$output = `/bin/pfexec /bin/mv $file.new $file 2>&1`;
	if ($? != 0) {
	  printf(gettext("Couldn't rename file %s")."\n", "$file.new");
	  print "error\n";
	  return;
	}
      }
    }

} elsif ($opline =~ "^move") {
    # Move an adapter to a new group
    my ($op, $group, $adp) = split(/ /, $opline);

    if ($adp eq "") {
	print gettext("Adapter name missing"),"\n";
	print "error\n";
	return;
    }

    if ($group eq "") {
	print gettext("Group name missing"),"\n";
	print "error\n";
	return;
    }

    my $file = "/etc/hostname.$adp";
    if (! -e $file) {
	# Configuration is messed up.  Adapter is ifconfig'd but
	# the hostname file is missing.
	printf(gettext("The operation cannot be completed because the adapter is in a group but the %s file is missing.  To fix this, remove the adapter from the group and then add it to the new group.").
	    "\n", $file);
	print "error\n";
	return;
    }

    if ($opline !~ /moveonly/) {
	# Do if_mpadm command to move the HA-IPs

	$cmd = "if_mpadm -d $adp";
	print "cmd=$cmd\n";
	$output = `/bin/pfexec /usr/sbin/$cmd 2>&1`;
	print $output;
	if ($? != 0) {
	    print "if_mpadm error";
	    return;
	}
    }

    # Perform the ifconfig task

    $cmd = "ifconfig $adp group $group";
    print "cmd=$cmd\n";

    $output = `/bin/pfexec /sbin/$cmd 2>&1`;
    print $output;
    if ($? != 0) {
	print "error\n";
	return;
    }

    # Create new hostname file
    my ($hostnamecmd, $newcmd);
    $hostnamecmd = `cat $file`;
    ($newcmd = $hostnamecmd) =~ s/group[\\\s]+\S+/group $group/gs;

    # Reconfiguration is identical, don't need to update the file
    if ($newcmd eq $hostnamecmd) {
	return;
    }

    if (! open(OUT, "| /bin/pfexec /bin/tee $file.new > /dev/null")) {
	printf(gettext("Couldn't open file %s")."\n", "$file.new");
	print "error\n";
	return;
    }
    print OUT $newcmd;
    if (! close(OUT)) {
	printf(gettext("Couldn't open file %s")."\n", "$file.new");
	print "error\n";
	return;
    }
    if (-e $file) {
	# Rename the old hostname file
	$output = `/bin/pfexec /bin/mv $file $file.old 2>&1`;
	if ($? != 0) {
	    printf(gettext("Couldn't rename file %s")."\n", "$file");
	    print "error\n";
	    return;
	}
    }
    # Replace the existing hostname file with the new one
    $output = `/bin/pfexec /bin/mv $file.new $file 2>&1`;
    if ($? != 0) {
	printf(gettext("Couldn't rename file %s")."\n", "$file.new");
	print "error\n";
	return;
    }
} else {
    print "Bad command $cmd\n";
    print "error\n";
}

exit(0);
