#!/usr/bin/perl 

#####
##### A program to copy SNARE rpms to a remote machine and install
##### http://www.intersectalliance.com/projects/Snare/
##### 
###
### Written by Ronald P. Reck DCTD/SAIC October 2004
### for Robert McGinn. SITO/SAIC 
### FCS Contract 
###
##### 
##### find info about expect.pm 
##### http://www.scripternet.com/perldoc/txt/Expect.txt
##### 
#####


# we need these libs
use Getopt::Std;
use Net::SCP::Expect;
use Expect;

# you can leave this out if you 
# comment out the network check
use Net::Ping;

# this script's version
$version="1.2";

# we need this value to create  unique file names
$time=time();
chomp($time);

# these commandline args
# are explained in the 
# subroutine print_help

getopts('vhm:u:p:');



# 
#How can I find out what Expect is doing?
#
#    If you set
#
#      $Expect::Exp_Internal = 1;
#    Expect will tell you very verbosely what it is receiving and sending,
#    what matching it is trying and what it found. You can do this on a
#    per-command base with
#
#      $exp->exp_internal(1);
#
#    You can also set
#
#      $Expect::Debug = 1;  # or 2, 3 for more verbose output
#
#    or
#
#      $exp->debug(1);
#    which gives you even more output.


# I am seeing the output of the command I spawned.  Can I turn that off?
#    Yes, just set
#
#      $Expect::Log_Stdout = 0;
#
#    to globally disable it or
#
#       $exp->log_stdout(0);

# turn off seeing what is typed
$Expect::Log_Stdout = 0;

# see if we make it chatter
$verbose=($opt_v);

# not going to far without these
$machine=($opt_m);
$username=($opt_u);
$password=($opt_p);
$root_password=($opt_r);

# some defaults if no values are explicitly set on the CLI
if (!defined $username) {$username ="root"}
if (!defined $password) {$password ="qwerty"}
if (!defined $root_password) {$root_password = $password}


# see if someone wants the help message
$help=($opt_h);
if ($help) {&print_help};


# lets see if we have enough information to do something
if (!defined $machine){
    print "\n You need to specify a machine name.\n";
    &print_help;
}


# remove pesky line returns
chomp($username);
chomp($password);
chomp($machine);
chomp($root_password);

if ($verbose) {
    print "\nThis is $0 version $version";
    print "\n-The time is $time";
    print "\n-Working with machine is $machine";
}

#$Expect::Exp_Internal = 0;
#$Expect::Log_Stdout   = 1;

# let's do it !
my $exp = Expect->spawn("ssh -l $username $machine")
    or die "Cannot spawn ssh: $!\n";


my $spawn_ok;
my $timeout  = 20;
my $prompt  = '[\]\$\>\#]\s$';     # A shell prompt 


###
### this is the heart of where stuff happens
###


# check the network connectivity of where we are going
&check_4_host($machine);

# determine which os
&determine_os($machine);




# determine which package goes with which os
&determine_package($os);

# let's log what's happening
$exp->log_file("installsnare-$machine-$time.txt.");

# copy the files to the remote machine
&copy_remote($machine,$file);

# unpackage the zip and tar
&unpack_remote($file);

# backup the boot paritition
&backup_boot_partition();

# install the rpms
&install_rpms($os);

# backup and copy the configs into place
&copy_configs();

# ok we are done; now reboot 
&reboot();

print "\n";
# we are completely done now exit.
exit 0;



sub copy_remote {
###
### this copys the rpm gzip to a remote machine
###
my  $host=shift;
my  $file=shift;
    $remote_file="/tmp/$file";
    my $scpe = Net::SCP::Expect->new;
    $scpe->login("$username", "$password");
    $scpe->scp("$file","$host:$remote_file");
}

sub unpack_remote {
###
### this unpacks the zip we just copied there
###
my $file=shift;
my $command="gzip -fd";
$exp->expect($timeout,
             [ qr'login: $', sub { $spawn_ok = 1; 
                                   my $fh = shift;
                                   $fh->send("$username\n");
                                   exp_continue; } ],
             [ 'assword: $', sub { my $fh = shift;
                                    print $fh "$password\n";
                                    exp_continue; } ],
             [ eof => sub {
                      if ($spawn_ok) {
                        die "ERROR: premature EOF in login.\n";
                      } else {
                        die "ERROR: could not spawn telnet.\n";
                      }
                    } ],
             [ timeout => sub { die "No login.\n"; } ],
             '-re', $prompt,
);
$exp->send("$command /tmp/$file\r");              

# need a different file name now that its unzipped
@parts=split(/\./,$file);
$file=(join "\.",$parts[0],$parts[1]);

if ($verbose) {
    print "\n-Working with unzipped file :$file:";
    }

$command="tar -xvf";
$exp->send("cd /tmp\r");              
$exp->send("$command /tmp/$file\r");              
#sleep 5;
$exp->expect($timeout, "$command\r\n"); # Wait for the command to be echoed back
$exp->expect($timeout, "\r\n");         # Now expect the output of the command

#print "The $command command returned: " . $exp->exp_before() . "\n";
#$exp->send("exit\r");
}

sub backup_boot_partition {
###
### this backs up root partition and needs r00t access
###

#$exp->send("su - \r");
#$exp->expect($timeout, "Password:");
#$exp->send("$root_password \r");
    if ($verbose){
	print "\n-Backing up boot partition.";
    }
$exp->send("mkdir -p /var/backup/ \r");
$exp->send("cd /var/backup \r");
$exp->send("tar -cf boot_partition-$time.tar /boot\r");
$exp->send("gzip -9 boot_partition-$time.tar\r");
    sleep 5;

# check that the backup happened
$exp->send("ls -l /var/backup/ \r");
$exp->expect($timeout, "boot_paritition");

# grab the results of the last expect call
undef ($test);
$test=$exp->exp_before();
    if ($test =~ /$time/){
	if ($verbose) {
	    print "\n QC:Boot partition backup found, quality control check passed.\n";
	    }
    }else{
	print "\n Quality control check failed. Backup of boot partition";
	print "\n did not occur."
	&qc_end();
    }



#$exp->send("exit\r");
}


sub print_help {
###
### Prints the help/usage message and exits.
###
    print "\n$0 version $version";
    print "\n$0 -m <machine> (best to use IP)";
    print "\n$0 -u <username> -p <password>";
    print "\n$0 -v put the script in verbose mode";
    print "\n";
    exit 0;
}






#sub install_rpms_redhat9 {
sub install_rpms{
my $os = shift;

# let's quit if something horrible happens and we dont know the OS
if (!defined $os) {
    $msg="\nProblem determining correct file for operating system $os.";
    &qc_exit($msg);
}


    if ($verbose) {
	print "\n-Installing rpms - for $os  this will take a while.";
    }

# if redhat 9
if ($os =~ /rh9/){
# install the 3 rpms for snare; one GUI, one core, and the kernel
$exp->send("rpm -i --force /tmp/snare/snare-gui-0.9.6-1.i386.rpm\r");
sleep 2;
$exp->send("rpm -i --force /tmp/snare/snare-core-0.9.6-1.i386.rpm\r");
sleep 2;
$exp->send("rpm -i --force /tmp/snare/kernel-smp-2.4.20-31.9.SNARE096.i686.rpm\r");
}elsif ($os =~ /rhelws/) {
# if redhat el ws 3.0
$exp->send("rpm -i --force /tmp/snare/snare-gui-0.9.6-1.i386.rpm\r");
sleep 2;
$exp->send("rpm -i --force /tmp/snare/snare-core-0.9.6-1.i386.rpm\r");
sleep 2;
$exp->send("rpm -i --force /tmp/snare/kernel-smp-2.4.21-15.EL.SNARE096.i686.rpm\r");
}


sleep 120;
    if ($verbose) {
	print "\n-Done installing rpms.";
    }
}


sub copy_configs {
###
### This copies the grub and audit configuration files into place
###


    if ($verbose){
	print "\n-Copying configuration files into place.";
    }
# backup the original audit configuration file
$exp->send("mv /etc/audit/audit.conf  /etc/audit/audit.$time.original\r");
sleep 1;
# copy the new audit configuration file into place
$exp->send("cp /tmp/snare/audit.conf  /etc/audit/audit.conf\r");
sleep 1;
# backup the original grub configuration file
$exp->send("mv /boot/grub/grub.conf  /boot/grub/grub.$time.original\r");
sleep 1;
# copy the new grub configuration into place
$exp->send("cp /tmp/snare/grub.conf  /boot/grub/grub.conf\r");
sleep 1;
}


sub reboot {
###
### reboot the machine
###

    if ($verbose){
	print "\n-Rebooting.\n\n";
    }
$exp->send("reboot\r");
    sleep 10;
}


sub qc_end {
###
### This runs when quality control checking finds something wrong.
###
    $msg=shift;
    print "\n **** Failure in quality control protection ****:";
    print "$msg";
    exit 0;
}


sub check_4_host{
###
### This checks to make sure we can reach the host before we try to do anything else.
###
    my $host=shift;
    my $p = Net::Ping->new();
    if ($p->ping($host)){
	if ($verbose) {
	    print "\n-$host is alive.\n" 
	    }	
	}else{
	$msg="\n Exiting because host :$host: is unreachable.\n";
	&qc_end($msg);
	}
    $p->close();

}


sub determine_os {
###
### determine which operating system we are dealing with.
###
my $machine=shift;
if ($verbose) {
print "\n-Checking operating system on machine $machine.";
}
$exp = Expect->spawn("ssh -l $username $machine");
$spawn_ok;
$exp->expect($timeout,
             [ qr'login: $', sub { $spawn_ok = 1; 
                                   my $fh = shift;
                                   $fh->send("$username\n");
                                   exp_continue; } ],
             [ 'assword: $', sub { my $fh = shift;
                                    print $fh "$password\n";
                                    exp_continue; } ],
             [ eof => sub {
                      if ($spawn_ok) {
                        die "ERROR: premature EOF in login.\n";
                      } else {
                        die "ERROR: could not spawn ssh.\n";
                      }
                    } ],
             [ timeout => sub { die "No login.\n"; } ],
             '-re', $prompt,
);

$command="uname -a";
$exp->send("$command\r");              
#sleep 5;
$exp->expect($timeout, "$command\r\n"); # Wait for the command to be echoed back
$exp->expect($timeout, "\r\n");  
$checkos=$exp->exp_before();
chomp($checkos);
if ($checkos =~ /2.4.20-8/) {
    if ($verbose) { print "\n-OS is stock Redhat 9.";}
    $os="rh9";
}elsif ($checkos =~ /2.4.21-4.EL/) {
    if ($verbose) { print "\n-OS is stock Redhat EL 3.0 .";}
    $os="rhelws";
}elsif($checkos =~ /2.4.18-14/ ) {
    if ($verbose) { print "\n-OS is stock Redhat 8.";}
    $os="rh8";
    $unsupported=1;
}elsif($checkos =~ /2.4.7-10/ ) {
    if ($verbose) { print "\n-OS is stock Redhat 7.3.";}
    $os="rh73";
    $unsupported=1;
}elsif($checkos =~ /SNARE/i ) {
    if ($verbose) { print "\n-Kernel already has snare installed..";}
    $os="SNARE";
    $unsupported=1;
}else {

# remote operating system is not recognized
$msg="\n OS $checkos is unknown or unsupported."
&qc_end($msg);
};

if ($unsupported){
    $msg="Operating system $os is unsupported.";
    &qc_end($msg);
    }else{
	if ($verbose) {print "\n-Operating system determined to be $os.";}
    }

}


sub determine_package{
###
### This determines which file to distribute for which operating system
###


    my $os=shift;
    if ($os =~ /rh9/) {
	$file="snare4rh9.tar.gz";
    }elsif ($os =~ /rhelws/){
	$file="snare4rhelws.tar.gz";
    }else{
	$msg="\nUnable to determine package for os $os";
	&qc_end($msg);
    }

}

#sub understand_errors {
#Cannot sync with child: No child processes at /usr/lib/perl5/site_perl/5.8.0/Expect.pm line 125.
#means hostname problems

# at /usr/lib/perl5/site_perl/5.8.0/Expect.pm line 733ease try again.
#means username problems
#}
