#!/usr/bin/perl 
# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*-
# vim: ts=4 sts=4 sw=4:

#ident "$Id: assign-network-devices,v 1.41 2015/01/09 14:19:45 gsc Exp $
# Copyright (c) 2014 Bruker BioSpin GmbH"";

use strict;
use warnings;
use English;
use Getopt::Long;
use File::Basename;
use File::Spec;
use File::Find ();
use Pod::Usage;
use Sys::Syslog qw(:standard :macros);  # standard functions & macros
use Data::Dumper;

use lib "/usr/share/bruker-setup";
use MAC_Vendor;

sub EditGrubConfig($);
sub WriteNetConfig_C5();
sub WriteNetConfig_C7();
sub ShowHashArray($%);
sub GetSmbiosSerialNumber();
sub ShowCurrentNetworkAssignment();
sub SwapMACAssignment();
sub StatusfileUpdate(@);
sub StatusfileInfo($);

my $program = basename($0);

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

####
# Supported options
my $help = 0;
my $man = 0;
my $info = 0;
my $save = 0;
my $show = 0;
my $test = 0;
my $syslog = 1;
my $kudzu = 0;
my $verbose = 0;

GetOptions(
	   'help|?' => \$help,
	   'man'  => \$man,
	   'verbose+'  => \$verbose,
	   'info'  => \$info,
	   'save'  => \$save,
	   'show'  => \$show,
	   'syslog!'  => \$syslog,
	   'test'  => \$test,
	   'hwconf'  => \$kudzu,
	  ) || pod2usage(2);
pod2usage(1) if $help;
pod2usage(-exitstatus => 0, -verbose => 2) if $man;

###
# Some info about myself
my @Version =  split(' ','$Revision: 1.41 $ ');
my $Version = $Version[1];
print "$program (Version $Version)\nCopyright (c) 2014 Bruker BioSpin GmbH\n\n" if $verbose;

###
# default file names
my $lshw	= '/usr/sbin/lshw';
my $hwconf	= '/etc/sysconfig/hwconf';
my $netcfg	= '/etc/sysconfig/network-scripts/ifcfg';
my $statusfile	= '/etc/sysconfig/bruker-setup';
my $grubcfg = '/etc/default/grub';


my $OS_Version = get_os_version();

print "OS_Version = $OS_Version\n" if $verbose;

####
# initalize cache of Vendor OUIs
####
my $oui = MAC_Vendor->new($verbose);

###
# Only curent State required ?
ShowCurrentNetworkAssignment() if ($show);

##
# On master image SELinux is enabled
my $selinuxenabled = 1;
#DESCRIPTION
#  selinuxenabled  Indicates  whether SELinux is enabled or disabled. 
#  It exits with status 0 if SELinux is enabled and 1 if it is not enabled.
system('selinuxenabled')  and $selinuxenabled = 0;

####
# Now get MAC addresses and PCI Bus info of Ethernet Cards
####
my %mac;
my %product;
my %manufacturer;
my %logicalname;
my @EtherDesc;
###
# Call lshw with NETWORK Class option to fetch MAC info
###
my $eth = 'none';
my $netif= -1 ;
my $swaprequired;

die "Missing $lshw\n" unless -x $lshw;
open(LSHW,"$lshw -quiet -class network|") || die "can't pipe to $lshw: $!\n";
while (<LSHW>)
{
    chomp;
    if ( /\*-network/ )
    {
	$netif++ ;
	$eth = 'eth' . $netif;
    }
    if ( /\*-network:(\d)/ )
    {
	$netif = $1;
	$eth = 'eth' . $netif;
    }

    $logicalname{$eth} = $1   if ( /logical name:\s([\w\d]*)/ );
    $mac{$eth} 	= $1 if (/serial:\s([0-9A-Fa-f:]+)/ );
    $product{$eth} 	= $1 if (/product:\s(.*)/ );
}
close(LSHW);

if ($verbose)
{
    ShowHashArray"MAC:",(%mac);
    ShowHashArray("Product:",%product);
    ShowHashArray("Logicalname:",%logicalname);
}

###
# Abort  session if MAC addresses are undefined
###
die "MAC addresses of eth0 and/or eth1 can't be found!\n" unless ($mac{'eth0'} and $mac{'eth1'});

unless ($logicalname{'eth0'} =~ /^eth/ )
{
    EditGrubConfig($grubcfg) && 
    die "TopSpin License Manager requires 'eth0' network device, but kernel options\n" . 
	" in '$grubcfg' " . 
	    " are not configured with net.ifnames=0\n" .
		"This Script changed the config file but reboot is required\n";
}
###
# show found assignment
###
print "Assignment:\n" if $verbose;
foreach my $dev (qw(eth0 eth1))
{
    $manufacturer{$dev} = $oui->mac2oui($mac{$dev});
    $manufacturer{$dev} = 'undefined'     unless  $manufacturer{$dev};
    print " $mac{$dev} \"$product{$dev}\" $manufacturer{$dev}\n" if $verbose && $manufacturer{$dev} ;
}

###
# Now assign MAC to manufacturer
foreach my $dev (qw(eth0 eth1))
{
    if ( $manufacturer{$dev} and  $manufacturer{$dev} =~ 'Hewlett.Packard')
    {
	my $HP_MAC = $mac{$dev};
	my $HP_PROD = $product{'eth0'};
	$mac{'HP'} = $HP_MAC;
	$product{'HP'} = $HP_PROD;
	$manufacturer{'HP'} = 'Hewlett-Packard';
    }
    else
    {
	my $OTHER_MAC = $mac{$dev};
	my $OTHER_PROD = $product{$dev};
	$mac{'OTHER'} = $OTHER_MAC;
	$product{'OTHER'} = $OTHER_PROD;
	$manufacturer{'OTHER'} = $manufacturer{$dev};
    }
}
unless ($mac{'HP'} )
{
    print "$program aborted:  Ehernet device assignment is unascertained!\n";
    exit 0;
}
print "HP\t$mac{'HP'}\nOTHER\t$mac{'OTHER'}\n" if $verbose;

my @hwconf;
if ($kudzu and $OS_Version eq 5)
{
    ###
    # read hwconf file created on master image
    ###
    print "read hwconf file\n" if $verbose;
    open(HWCONF,"$hwconf") || die "can't open $hwconf: $!\n";
    @hwconf = <HWCONF>;
    close(HWCONF);
}

my $status = 'NO';

openlog('bruker-setup', 'nofatal,pid', 'user') if $syslog;

if (SwapMACAssignment())
{
    my $msg0 = "Network HWADDR $mac{'OTHER'} by $manufacturer{'OTHER'} assigned to eth0";
    my $msg1 = "Network HWADDR $mac{'HP'}  by $manufacturer{'HP'} assigned to eth1";

    print $msg0,"\n";
    print $msg1,"\n";
    if ($syslog)
    {
	syslog(LOG_INFO, $msg0);
	syslog(LOG_INFO, $msg1);
    }

    if ($kudzu and $OS_Version eq 5)
    {
	my $class = '';
	my $device = '';
	my $driver = '';
	my $hwaddr = '';

	open(OUT,">$hwconf") || die "can't open $hwconf: $!\n" unless $test;
	foreach my $line (@hwconf)
	{
	    if ( $line eq '-')	# reset for next CLASS
	    {
		undef $class;
		undef $device;
		undef $driver;
		undef $hwaddr;
	    }
	    $class 	= $1 if ($line =~ /^class:\s(\w*)/ );
	    if (defined $class and $class eq 'NETWORK')
	    {
		$driver	= $1 if ($line =~ /^driver:\s(.*)/ );
		$device 	= $1 if ($line =~ /^device:\s([\w\d]*)/ );
		$hwaddr 	= $1 if ($line =~ /^network.hwaddr:\s([0-9A-Fa-f:]+)/ );
		if (defined $device and $hwaddr)
		{
		    if ($device eq 'eth0' and $hwaddr eq $mac{'HP'})
		    {
			print OUT "network.hwaddr: $mac{'OTHER'}\n" unless $test;
			undef $device;
			next;
		    }
		    if ($device eq 'eth1' and $hwaddr eq $mac{'OTHER'})
		    {
			print OUT "network.hwaddr: $mac{'HP'}\n";
			undef $device;
			next;
		    }
		}
	    }
	    print OUT $line unless $test;
	}
	close(OUT) unless $test;
    }
    ###
    # write OS specific Network configurations
    WriteNetConfig_C5() if ($OS_Version eq 5 and not $test);
    WriteNetConfig_C7() if ($OS_Version eq 7 and not $test);

    StatusfileUpdate('ETH_DEVICES_ARE_FIXED','YES');
}

####
# remove all ifcfg-eth?.bak files in /etc/sysconfig
####
File::Find::find({wanted => \&remove_eth_bak}, '/etc/sysconfig');

closelog  if $syslog;

exit;

###################
##               ##
##  Subroutines  ##
##               ##
###################
sub get_os_version
{
    my $querycmd = 'rpm -qf --queryformat %{Name} /etc/redhat-release';
    my $redhat = `$querycmd`;

    chomp $redhat;
    $querycmd = 'rpm -q --queryformat %{version} ' . $redhat;

    my $redhat_release = `$querycmd`;

    my $rh_release  = (int $redhat_release);
    return $rh_release;
}


sub GetSmbiosSerialNumber()
{
    my $dmi		= '/usr/sbin/dmidecode';
    die "Missing $dmi\n" unless -x $dmi;

    my $ser_num = `$dmi -s system-serial-number`;
    chomp($ser_num);
    my $product = `$dmi -s system-product-name`;
    chomp($product);
    if ($product  =~ (/^VMware Virtual Platform/))
    {
	use Digest::MD5 qw(md5_hex);
	if ( $ser_num =~ /VMware-(.*)\n/ )
	{
	    $ser_num = 'VM' . substr(md5_hex($1),0,8);
	}
    }
    elsif ($product  =~ (/^VirtualBox/))
    {
	if ( `dmidecode -s system-uuid` =~ /([\dA-Z]*)/ )
	{
	    $ser_num = 'VB' . $1;
	}
    }
    elsif ($product  =~ (/^HP Z\d\d\d Workstation$/))
    {
	print "$product\n" if $verbose;
    }
    else
    {
	die "Setup of <$product> not supported.\n";
    }

    return $ser_num;
}


sub remove_eth_bak
{
    /^ifcfg-eth.*\.bak\z/s
	&& unlink($name)
	    && ($syslog && syslog(LOG_INFO, "file %s removed",$name));

}


####
# Show current network assignment in ifcfg-eth? files
####
sub ShowCurrentNetworkAssignment()
{
    my $is_valid_workstation;
    foreach my $eth (qw(eth0 eth1))
    {
        open IFCFG, "<$netcfg-$eth" or next;
        while (<IFCFG>)
	{
	    if (/^HWADDR=([0-9a-fA-F:]*)/)
	    {
		my $current_addr = $1;
		my $manufacturer = $oui->mac2oui($current_addr );
		print "$eth: $current_addr \t Vendor :: $manufacturer\n";
		$is_valid_workstation++;
	    }
	}
	close(IFCFG);
    }
    print "Configuration does not include eth0 or eth1\n" unless $is_valid_workstation;

    system('firewall-cmd', '--get-active-zones') if ($OS_Version eq 7);

    exit 0;
}

sub EditGrubConfig($)
{
    my $grubcfg = shift;
    open(GRUB,"$grubcfg")  || die "$grubcfg: $!\n";
    my @lines = <GRUB>;
    close(GRUB);

    my $netifnames = 0;
    open(GRUB,">$grubcfg")  || die "$grubcfg: $!\n";
    foreach my $line (@lines)
    {
	if ($line =~ /^GRUB_CMDLINE_LINUX="(.*)"/)
	{
	    my $options = $1;
	    unless ($options =~ /net.ifnames=0/)
	    {
		print GRUB "GRUB_CMDLINE_LINUX=\"$options net.ifnames=0\"\n";
		print "\nGRUB_CMDLINE_LINUX=\"$options net.ifnames=0\"\n  changed.\n" if $verbose;
		$netifnames = 1;
		next;
	    }
	}
	print GRUB $line;
    }
    close(GRUB);

    my $bioscfg = '/boot/grub2/grub.cfg';
    my $ueficfg = '/boot/efi/EFI/centos/grub.cfg';
    $grubcfg = $bioscfg if (-f $bioscfg);
    $grubcfg = $ueficfg if (-f $ueficfg);

    print "execute grub2-mkconfig:"  if $verbose;
    system("grub2-mkconfig -o $grubcfg") if $netifnames;

    return $netifnames;
}

sub WriteNetConfig_C5()
{
    use Time::localtime;

    my $now = ctime();
    my $created = "Created by  $program $now";
    my $hostname = StatusfileInfo("DEFAULT_HOSTNAME");
    my $network = '/etc/sysconfig/network';
    my $dhclient = '/etc/dhclient-eth0.conf';
    my $defaultnetwork  = '/etc/sysconfig/networking/profiles/default/network';
    my $netprofile = '/etc/sysconfig/networking/profiles/default/ifcfg';
    my $netdevice  = '/etc/sysconfig/networking/devices/ifcfg';

    my $resolvconf  = '/etc/sysconfig/networking/profiles/default/resolv.conf';
    unlink "$resolvconf";

    open(DHCLIENT, ">$dhclient") or die "Cannot open $network: $!";
    print DHCLIENT "send host-name \"$hostname\";";  # temporary RHL ifup addition\n";
    close(DHCLIENT) ;

    open(NETWORK, ">$network") or die "Cannot open $network: $!";
    print NETWORK "NETWORKING=yes\n";
    print NETWORK "HOSTNAME=$hostname\n";
    close(NETWORK) ;

    open(NETWORK, ">$defaultnetwork") or die "Cannot open $defaultnetwork: $!";
    print NETWORK "HOSTNAME=$hostname\n";
    close(NETWORK) ;
    ####
    # remove linked files
    unlink "$netprofile-eth0";
    unlink "$netprofile-eth1";
    unlink "$netdevice-eth0";
    unlink "$netdevice-eth1";

    open(IFCFG, ">$netcfg-eth0" ) or die "Cannot open $netcfg-eth0: $!";
    print IFCFG "# ", $product{'OTHER'},"\n";
    print IFCFG "# $created\n";
    print IFCFG "TYPE=Ethernet\n";
    print IFCFG "DEVICE=eth0\n";
    print IFCFG "HWADDR=$mac{'OTHER'}\n";
    print IFCFG "BOOTPROTO=dhcp\n";
    print IFCFG "DHCP_HOSTNAME=$hostname\n";
    print IFCFG "ONBOOT=yes\n";
    print IFCFG "LINKDELAY=60\n";
    print IFCFG "IPV6INIT=no\n";
    print IFCFG "PEERDNS=yes\n";
    close(IFCFG);
    ###
    # fix SELinux context
    system ('chcon','-t','etc_t',"$netcfg-eth0") if $selinuxenabled;
    ####
    # create file links again
    link("$netcfg-eth0","$netprofile-eth0");
    link("$netcfg-eth0","$netdevice-eth0");

    open(IFCFG, ">$netcfg-eth1" ) or die "Cannot open $netcfg-eth1: $!";
    print IFCFG "# ", $product{'HP'},"\n";
    print IFCFG "# $created\n";
    print IFCFG "TYPE=Ethernet\n";
    print IFCFG "DEVICE=eth1\n";
    print IFCFG "HWADDR=$mac{'HP'}\n";
    print IFCFG "BOOTPROTO=none\n";
    print IFCFG "NETMASK=255.255.255.0\n";
    print IFCFG "IPADDR=149.236.99.1\n";
    print IFCFG "ONBOOT=yes\n";
    print IFCFG "USERCTL=no\n";
    print IFCFG "IPV6INIT=no\n";
    print IFCFG "PEERDNS=yes\n";
    close(IFCFG);
    ###
    # fix SELinux context
    system ('chcon','-t','etc_t',"$netcfg-eth1") if $selinuxenabled;
    ####
    # create file links again
    link("$netcfg-eth1","$netprofile-eth1");
    link("$netcfg-eth1","$netdevice-eth1"); 
    system ('lokkit', '--quiet', '--trust=eth1');
}

sub WriteNetConfig_C7()
{
    use Time::localtime;

    my $now = ctime();
    my $created = "Created by  $program $now";
    my $hostname = StatusfileInfo("DEFAULT_HOSTNAME");
    my $dhclient = '/etc/dhclient-eth0.conf';
    my $netdevice  = '/etc/sysconfig/networking/devices/ifcfg';
    my $uuid;
    open(my $filefd, ">$dhclient") or die "Cannot open $dhclient: $!";
    print $filefd "send host-name \"$hostname\";";  # temporary RHL ifup addition\n";
    close($filefd) ;

    open(IFCFG, ">$netcfg-eth0" ) or die "Cannot open $netcfg-eth0: $!";
    print IFCFG "# ",$product{'eth0'},"\n";
    print IFCFG "# $created\n";
    print IFCFG "TYPE=Ethernet\n";
    print IFCFG "DEVICE=eth0\n";
    print IFCFG "HWADDR=$mac{'OTHER'}\n";
    print IFCFG "BOOTPROTO=dhcp\n";
    print IFCFG "ONBOOT=yes\n";
    print IFCFG "NM_CONTROLLED=yes\n";
    print IFCFG "LINKDELAY=60\n";
    print IFCFG "IPV6INIT=yes\n";
    print IFCFG "IPV6_AUTOCONF=yes\n";
    print IFCFG "IPV6_DEFROUTE=yes\n";
    print IFCFG "IPV6_PEERDNS=yes\n";
    print IFCFG "IPV6_PEERROUTES=yes\n";
    print IFCFG "IPV6_FAILURE_FATAL=no\n";

    print IFCFG "PEERDNS=yes\n";

    print IFCFG "NAME=eth0\n";
    $uuid = `uuidgen`;
    chomp($uuid);
    print IFCFG 'UUID="',$uuid,'"',"\n";

    close(IFCFG);
    ###
    # fix SELinux context
    # $ ls -lZ /etc/sysconfig/network-scripts/ifcfg-eth0
    # -rw-r--r--. root root system_u:object_r:net_conf_t:s0  /etc/sysconfig/network-scripts/ifcfg-eth0

    my @chcon = qw(chcon -u system_u -r object_r -t net_conf_t);
    system (@chcon,"$netcfg-eth0") if $selinuxenabled;

    open(IFCFG, ">$netcfg-eth1" ) or die "Cannot open $netcfg-eth1: $!";
    print IFCFG "# ",$product{'eth1'},"\n";
    print IFCFG "# $created\n";
    print IFCFG "TYPE=Ethernet\n";
    print IFCFG "DEVICE=eth1\n";
    print IFCFG "HWADDR=$mac{'HP'}\n";
    print IFCFG "BOOTPROTO=none\n";
    print IFCFG "NM_CONTROLLED=no\n";
    print IFCFG "NETMASK=255.255.255.0\n";
    print IFCFG "IPADDR=149.236.99.1\n";
    print IFCFG "ONBOOT=yes\n";
    print IFCFG "USERCTL=no\n";
    print IFCFG "IPV6INIT=no\n";
    print IFCFG "PEERDNS=yes\n";
    print IFCFG "NAME=eth1\n";
    print IFCFG "ZONE=trusted\n";
    $uuid = `uuidgen`;
    chomp($uuid);
    print IFCFG 'UUID="',$uuid,'"',"\n";
    close(IFCFG);
    ###
    # fix SELinux context
    system (@chcon,"$netcfg-eth1") if $selinuxenabled;

#    system ('nmcli','connection','reload');

#    system('firewall-cmd', '--change-interface=eth1', '--zone=trusted'); 
#    system('firewall-cmd', '--permanent', '--change-interface=eth1', '--zone=trusted'); 

}

sub StatusfileUpdate(@)
{
    my $parameter = shift;
    my $value = shift;

    print "$statusfile ::  StatusfileUpdate($parameter,$value)\n" if $verbose;

    if ( open LOG, "+<$statusfile" )
    {
        flock LOG, 2;
        my @data = <LOG>;
        seek LOG, 0, 0;
        truncate LOG, 0;
	my @newdata;
	foreach my $line (@data)
	{
	    next  if ($line =~ /^$parameter=/);
	    push(@newdata,$line);
	}
	push(@newdata,"$parameter=$value\n");
	seek LOG, 0, 0;
	print LOG @newdata;
        close LOG;
	print @newdata,"\n" if $verbose;

    }
    else
    {
	if ( open LOG, ">$statusfile" )
	{
	    print LOG "$parameter=$value\n";
	    close(LOG);
	}
	else
	{
	    die "$statusfile:$!\n";
	}
    }
}

sub StatusfileInfo($)
{
    my $parameter = shift;
    print "$statusfile :: StatusfileInfo($parameter)\n" if $verbose;
    my $value = '';
    if ( open LOG, "<$statusfile" )
    {
        my @data = <LOG>;
	foreach my $line (@data)
	{
	    print "$line" if $verbose;
	    if ($line =~ /^($parameter)=(\S*)$/)
	    {
		$value = $2;
		last;
	    }
	}
        close LOG;
    }
    print "return $value\n" if $verbose;

    return $value;
}

sub ShowHashArray($%)
{
    my $head = shift;
    my %hash = @ARG;
    print "$head:\n";
    while (my($key,$val) = each(%hash))
    {
	print " $key => $val\n";
    }
}

sub SwapMACAssignment()
{
    if ($manufacturer{'eth0'} =~ 'Hewlett.Packard') # wrong assignment
    {
	my $msg = "network.hwaddr of eth0 <=> eth1 will be swapped ";
	syslog(LOG_INFO, $msg) if $syslog;
	return 1;
    }
    elsif ($manufacturer{'eth1'} =~ 'Hewlett.Packard') # correct assignment
    {
	my $msg = "network.hwaddr $mac{'eth1'} already assigned as eth1 on motherboard";
	$product{'HP'} = $product{'eth1'};
	$product{'OTHER'} = $product{'eth1'};
	syslog(LOG_INFO, $msg) if $syslog;
    }
    my $eth0 = $netcfg . '-eth0';
    my $eth1 = $netcfg . '-eth1';
    return 1 unless -f $eth0 and -f $eth1;
    return 0;
}

__END__

=head1 NAME

assign-network-devices - Assign 'Hewlett Packard' Vendors  OUI to eth1

=head1 SYNOPSIS

assign-network-devices [options]

Options:

     -help            brief help message
     -man             prints the manual page
     -verbose         create verbose output
     -save            modifiy /etc/sysconfig/network-scripts/ifcfg-eth[01]
     -show            show current assingment and vendor


=head1 OPTIONS

=over 8

=item B<-help>

    Print a brief help message and exits.

=item B<-man>

    Prints the manual page and exits.


=item B<-verbose>

    Print additional debug information

=item B<-save>

    Modifiy /etc/sysconfig/network-scripts/ifcfg-eth* files
    and change MAC addresse of ifcfg-eth1 to the device which
    has 'Hewlett Packard' as Vendors  OUI

=item B<-show>

    Display the Vendors  OUI of the HWADDR in 
    /etc/sysconfig/network-scripts/ifcfg-eth* files

=back


=head1 DESCRIPTION

assign-network-devices - Assign MAC according to Vendors  OUI to ifcfg-eth[01]
 
This script is required during system boot by the service bruker-setup
to assing to MAC addresses in ifcfg-eth0 and ifcfg-eth1. It assumes that
the Vendors  OUI of 'Hewlett Packard' is used on the motherboard, which 
should be connected to the spectrometer.

=cut

