#!/usr/bin/perl -w

#
# "SystemImager" 
#
#  Copyright (C) 1999-2001 Brian Elliott Finley 
#                          <brian.finley@baldguysoftware.com>
#  Copyright (C) 2002 Bald Guy Software 
#                     <brian.finley@baldguysoftware.com>
#
#  $Id: getimage,v 1.133 2004/03/02 23:35:30 brianfinley Exp $
#
#  Others who have contributed to this code (in alphabetical order):
#    Phil Champon <pchampon@valueweb.net>
#    Sean Dague <sean@dague.net>
#    Ian McLeod <ian@valinux.com>
#    James Oakley <joakley@solutioninc.com>
#    Laurence Sherzer <lsherzer@gate.net>
#    Wesley Smith <wessmith@engr.sgi.com>
#    Curtis Zinzilieta <czinzilieta@valinux.com>
#

# TODO
# - touch image directory after each getimage

# set system path for system() calls
$ENV{PATH} = "/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin";

# version
$version_number="SYSTEMIMAGER_VERSION_STRING";

# miscellaneous variables and what not
# NIS domain name will be figured out automatically (if it exists)
$nisdomain="";

# use the long options module to allow us to use, well, long options ;)
use lib "USR_PREFIX/lib/systemimager/perl";
use Getopt::Long;
use SystemImager::Server;
use SystemImager::Common;
use SystemImager::Config;
use SystemImager::Options;
use vars qw($config $VERSION);

my $config_dir = "/etc/systemimager";

### BEGIN parse the config file ###
#
my $autoinstall_script_dir = $config->autoinstall_script_dir();
my $rsync_stub_dir = $config->rsync_stub_dir();
my $rsyncd_conf = $config->rsyncd_conf();
my $default_image_dir = $config->default_image_dir();

if (!$autoinstall_script_dir) {
    die "AUTOINSTALL_SCRIPT_DIR not defined in the config file.";
}
if (!$rsync_stub_dir) { die "RSYNC_STUB_DIR not defined in the config file."; }
if (!$rsyncd_conf) { die "RSYNCD_CONF not defined in the config file."; }
if (!$default_image_dir) {
    die "DEFAULT_IMAGEDIR not defined in the config file.";
}
### END parse the config file

### BEGIN Program ###

# figure out the program name
$0  =~ /(.*)\/+([^\/]*)$/;
$program_name = $2;

$version_info = <<"EOF";
$program_name (part of SystemImager) v$version_number

EOF

$version_info .= SystemImager::Options->copyright();

$get_help = "          Try \"$program_name -help\" for more options.";

# set help information
$help_info = $version_info .  SystemImager::Options->getimage_options_header();
$help_info = $help_info .  SystemImager::Options->generic_options_help_version();
$help_info = $help_info .  SystemImager::Options->getimage_options_body();
$help_info = $help_info .  SystemImager::Options->generic_footer();


my $golden_client;
my $imageserver;
my $ip_assignment_method;
my $image;
my $exclude_file;
my $update_script;
my $post_install = "beep"; # the default

# It is not intended that auto_install_script_conf be passable as an option on
# the getimage command line.  See mkautoinstallscript if you want to specify 
# an alternate config file. -BEF-
#
my $auto_install_script_conf;   

### BEGIN evaluate options ###
GetOptions( 
    "golden-client=s"   => \$golden_client,
    "server=s"          => \$imageserver,
    "image=s"           => \$image,
    "directory=s"       => \$default_image_dir,
    "ip-assignment=s"   => \$ip_assignment_method,
    "exclude=s"         => \my @excludes,
    "exclude-file=s"    => \$exclude_file,
    "update-script=s"   => \$update_script,
    "post-install=s"    => \$post_install,
    "no-listing"        => \$no_listing,
    "ssh-user=s"        => \$ssh_user,
    "log=s"             => \$log,
    "help"              => \$help,
    "version"           => \$version,
    "quiet"             => \$quiet
) || die "$help_info";

$update_script = lc $update_script;

#if requested, print help information
if ($help) { 
  print "$help_info";
  exit 0;
}

# if requested, print version and copyright information
if ($version) {
  print "$version_info";
  exit 0;
}

# be sure program is run by root
SystemImager::Common->check_if_root();

if($imageserver){
  die "\n$program_name: -server is now depricated.  Try cpimage command instead.\n$get_help\n\n";
}

# be sure $golden_client name doesn't start with a hyphen
if ($golden_client) {
  if ($golden_client =~ /^-/) { 
    die "\n$program_name: Golden client name can't start with a hyphen.\n$get_help\n\n";
  }
  $source_host = $golden_client;
}

# both a source host and image must be set.
unless ($source_host and $image) {
    die "\n$program_name: You must specify -golden-client and -image.\n$get_help\n\n";
}


# be sure $image doesn't start with a hyphen
if($image){
  if ($image =~ /^-/) { 
    die "\n$program_name: Image name can't start with a hyphen.\n$get_help\n\n";
  }
}

# be sure $default_image_dir is an absolute path starting with /
unless ($default_image_dir =~ /^\//) { 
  die "\n$program_name: -directory must be an absolute path starting with \"/\".\n$get_help\n\n";
}

# be sure each $exclude is an absolute path starting with /
foreach my $exclude (@excludes){
  unless($exclude =~ /^\//){ 
    die "\n$program_name: -exclude must be an absolute path starting with \"/\".\n$get_help\n\n";
  }
}

# be sure $update_script was passed a proper option
unless(
       ($update_script eq ""   )
    or ($update_script eq "yes")
    or ($update_script eq "no" )
) { die "\n$program_name: -update-script must be yes or no.\n$get_help\n\n"; }

SystemImager::Server->validate_ip_assignment_option($ip_assignment_method);
SystemImager::Server->validate_post_install_option($post_install);

# only golden client or server may be set
if (@excludes and $exclude_file) {
  die "\n$program_name: Either use -exclude or -exclude-file but not both.\n$get_help\n\n";
}

# if -exclude-file is used, exclude file must exist
if ($exclude_file) {
  unless(-e $exclude_file){
    die "\n$program_name: I can\'t find the exclude file specified by -exclude-file!\n$get_help\n\n";
  }
}
### END evaluate options ###

# Set script_name.
my $script_name = $image;

# fill in variables based on options passed
$final_exclude_file = "/tmp/.exclude.$image";
$imagedir = "$default_image_dir/$image";

$warning =  <<"EOF";
This program will get the \"$image\" system image from \"$source_host\"
making the assumption that all filesystems considered part
of the system image are using ext2, ext3, jfs, FAT, reiserfs, or xfs.

This program will not get /proc, NFS, or other filesystems
not mentioned above.

*********************************** WARNING *********************************** 
All files retrieved from a golden client are, by default, made accessible to 
anyone who can connect to the rsync port of this machine.  See rsyncd.conf(5)
for details on restricting access to these files on the imageserver.  See the
systemimager-ssh package for a more secure (but less effecient) method of 
making images available to clients.
*********************************** WARNING *********************************** 

See \"getimage -help\" for command line options.

EOF

# give warning
if (!$quiet) {
    system("clear");
    print $warning;
    print "Continue? ([y]/n): ";
    $continue = SystemImager::Common->get_response('y');
    ($continue ne "n") or die "$program_name: No files were modified.\n";
    print "\n";
}

if (! -d "$default_image_dir")  {
    mkdir("$default_image_dir", 0750) or die "$program_name: Can't make directory $default_image_dir\n";
}

if (-d "$imagedir") {
    if (!$quiet) {
        print "An image named \"$image\" already exists...\n";
        print "Update existing image? ([y]/n): ";
        $continue = SystemImager::Common->get_response('y');
        ($continue ne "n") or die "$program_name: No files were modified.\n";
        print "\n";
    }
} else  {
    mkdir("$imagedir", 0777) || 
	die "$program_name: Can't make directory $imagedir\n";
}

# Set default rsync port number
$port="873";

# If we're using SSH, go ahead and establish port forwarding
if($ssh_user) {
  # Get a random port number (normal rsync port won't work if rsync daemon is running)
  my $port_in_use="yes";
  until ( $port_in_use eq "no" )
  {
    $port_in_use="no";
    $port = rand 60000;
    $port =~ s/\..*//;

    # Be sure port isn't reserved
    $file="/etc/services";
    open (FILE, "<$file") || die ("$0: Couldn't open $file for reading\n");
      while (<FILE>) {
        if (/$port/) { 
          $port_in_use="yes";
          next;
        }
      }
    close FILE;
  
    # Be sure port isn't in use
    open (NETSTAT, "netstat -tn |");
    while (<NETSTAT>) {
      (my $junk, my $port_and_junk) = split (/:/);
      if ($port_and_junk) { 
        (my $netstat_port, my $other_junk) = split (/ +/, $port_and_junk);
        if ($netstat_port = /$port/) { 
          $port_in_use="yes";
          next;
        }
      }
    }
  }

  # Setup the port forwarding
  $command="ssh -f -l $ssh_user -L $port:$source_host:873 $source_host sleep 5";
  $rc = 0xffff & system($command);
  if ($rc != 0) { 
    print "FATAL: Failed to establish secure port forwarding to $source_host!\n";
    die   "       Be sure that you can run \"ssh -l $ssh_user $source_host\" successfully.\n";
  }

  # and change the source host to point to localhost so we can use the port forwarding
  $source_host="127.0.0.1";
}


### BEGIN golden client stuff ###
if($source_host) {
  # get /etc/systemimager/mounted_filesystems from $source_host
  if (!$quiet) {
    print "Retrieving /etc/systemimager/mounted_filesystems from $source_host to check for mounted filesystems...\n";

    my $directory="${imagedir}/etc/";
    unless (-d "$directory") { mkdir("$directory", 0755) || die "$program_name: Can't make directory $directory\n"; }

    $directory="${imagedir}/etc/systemimager/";
    unless (-d "$directory") { mkdir("$directory", 0755) || die "$program_name: Can't make directory $directory\n"; }

    $command = "rsync -av --numeric-ids";
    if ($log) { $command = $command . qq( --log-format="$log"); }
    if ($ssh_user) { $command = $command . " --bwlimit=10000"; }
    $command =  $command . " rsync://${source_host}:${port}/root/etc/systemimager/mounted_filesystems ${imagedir}/etc/systemimager/mounted_filesystems";

    open(RSYNC, "$command|");
    print "------------- $source_host mounted_filesystems RETRIEVAL PROGRESS -------------\n";
    while (<RSYNC>) {
      print;
    }
    print "------------- $source_host mounted_filesystems RETRIEVAL FINISHED -------------\n";
    close(RSYNC);
  } else {

    $directory="${imagedir}/etc/";
    unless (-d "$directory") { mkdir("$directory", 0755) || die "$program_name: Can't make directory $directory\n"; }

    $directory="${imagedir}/etc/systemimager/";
    unless (-d "$directory") { mkdir("$directory", 0755) || die "$program_name: Can't make directory $directory\n"; }

    $command = "rsync -a --numeric-ids";
    if ($log) { $command = $command . qq( --log-format="$log"); }
    if ($ssh_user) { $command = $command . " --bwlimit=10000"; }
    $command = $command . " rsync://${source_host}:${port}/root/etc/systemimager/mounted_filesystems ${imagedir}/etc/systemimager/mounted_filesystems";

    system($command);
  }
  
  # $? is the return value from rsync
  if ($?) {
    print "Failed to retrieve /etc/systemimager/mounted_filesystems from $source_host.\n";
    print "$program_name: Have you run \"prepareclient\" on $source_host?\n";
    print "          If you see the message \"unrecognised option\" above, check\n";
    print "          http://systemimager.org/download/ to be sure that you are running\n";
    die   "          the recommended version of rsync.\n";
  }
  
  # create list of filesystems to *not* get
  $file="${imagedir}/etc/systemimager/mounted_filesystems";
  open (FILE, "<$file") || die ("$program_name: Couldn't open $file for reading!\n");
    @mounted_filesystems = <FILE>;
  close FILE;

  $file="$final_exclude_file";
  open (FINAL_EXCLUDE_FILE, ">$file") || die "$program_name: Couldn't open $file for writing!\n";
    @mounted_filesystems = grep (!/\s+ext2\s+/, @mounted_filesystems);
    @mounted_filesystems = grep (!/\s+ext3\s+/, @mounted_filesystems);
    @mounted_filesystems = grep (!/\s+reiserfs\s+/, @mounted_filesystems);
    @mounted_filesystems = grep (!/\s+jfs\s+/, @mounted_filesystems);
    @mounted_filesystems = grep (!/\s+xfs\s+/, @mounted_filesystems);
    @mounted_filesystems = grep (!/\s+vfat\s+/, @mounted_filesystems);
    @mounted_filesystems = grep (!/\s+fat\s+/, @mounted_filesystems);
    print FINAL_EXCLUDE_FILE "\n# Automatic exclusions made by SystemImager.\n";
    
    foreach (@mounted_filesystems) {
      /\S+\s+\S+\s+(\S+)\s+/;
      my $mount_point=$1;
      print FINAL_EXCLUDE_FILE "$mount_point/*\n";
    }
    if(@excludes) { 
     print FINAL_EXCLUDE_FILE "\n# Exclusions from -exclude on the command line.\n";
      foreach $exclude (@excludes) {
        print FINAL_EXCLUDE_FILE "$exclude\n";
      }
    }
    if($exclude_file) { 
      print FINAL_EXCLUDE_FILE "\n# Exclusions from -exclude-file.\n";
      $file="$exclude_file";
      open (EXCLUDE_FILE, "<$file") || die "$program_name: Couldn't open $file for reading!\n";
        while(<EXCLUDE_FILE>){
          print FINAL_EXCLUDE_FILE "$_\n";
	}
      close EXCLUDE_FILE;
    }
  close FINAL_EXCLUDE_FILE;

  # compile rsync options
  $options = "--delete --delete-excluded --exclude-from=$final_exclude_file";
  if ($log) { $options = $options . qq( --log-format="$log"); }
  if ($ssh_user) { $options = $options . " --bwlimit=10000"; }
  $options = $options . " rsync://${source_host}:${port}/root/ $imagedir/";
}
### END golden client stuff ###

### BEGIN generic image retrieval stuff ###
# Get the image from the specified host
if (!$quiet) {
    print "\n\nRetrieving image $image from $source_host\n";
    open (RSYNC, "rsync -aHSv --numeric-ids $options |");
    print "------------- $image IMAGE RETRIEVAL PROGRESS -------------\n";
    while (<RSYNC>) {
        print $_;
    }
    print "------------- $image IMAGE RETRIEVAL FINISHED -------------\n";
    close (RSYNC);
} else {
    $command = qq(rsync -aHS --numeric-ids $options);
    system($command);
}

# $? is the return value from rsync
if ($?) {
    die "$program_name: Failed to retrieve image $image from $source_host.\n";
}


if(!$quiet) {
    print "\nPress <Enter> to continue...";
    <STDIN>;
}

unlink $final_exclude_file || die "$program_name: Removal of file $final_exclude_file failed\n";

# Add entry to image server's rsyncd.conf if necessary
SystemImager::Server->create_image_stub($rsync_stub_dir, $image, $imagedir) or 
    die "$program_name: Cannot create rsync stub entry in $rsync_stub_dir";

SystemImager::Server->gen_rsyncd_conf($rsync_stub_dir, $rsyncd_conf) or
    die "$program_name:  Cannot generate $rsyncd_conf";

### END generic image retrieval stuff ###

# Add image entry to flamethrower.conf file
my $flamethrower_conf = "/etc/systemimager/flamethrower.conf";
my $entry_name = $image;
my $new_entry_data = "[$image]\nDIR = $imagedir\n";
if(-e $flamethrower_conf) {
    SystemImager::Common->add_or_delete_conf_file_entry($flamethrower_conf, $entry_name, $new_entry_data) or 
        die "$program_name: Cannot add entry to $flamethrower_conf";

    # Add override entry to flamethrower.conf file
    $entry_name = "override_" . $image;
    $new_entry_data = "[override_$image]\nDIR = /var/lib/systemimager/overrides/$image\n";
    SystemImager::Common->add_or_delete_conf_file_entry($flamethrower_conf, $entry_name, $new_entry_data) or 
        die "$program_name: Cannot add entry to $flamethrower_conf";
}


### BEGIN Overwrite $script_name.master? ###
if( (!$quiet) and (!$update_script) and (-e "$autoinstall_script_dir/$script_name.master") ) {
    system("clear");
    print "Update Autoinstall Script?\n";
    print "--------------------------\n\n";
    print "An autoinstall script for this image already exists.  It is recommended\n";
    print "that you update this autoinstall script, unless you have customized it.\n";
    print "(You will know if you have customized it.)\n\n";
    print "Would you like to update the autoinstall script for this image? ([y]/n): ";

    $update_script = SystemImager::Common->get_response('yes');
    $update_script = lc $update_script;

    # if user actually hits "y", deal with it
    if($update_script eq "y") { $update_script = "yes"; }
}

# If it just ain't there yet, make it and don't even ask about it.
elsif(! -e "$autoinstall_script_dir/$script_name.master") {
    $update_script="yes";
}

# if we don't need to update the master script, then exit -- we're done
unless($update_script eq "yes") { exit 0; }

### END Overwrite $script_name.master? ###


### BEGIN Got NIS? ###
$file="$imagedir/etc/sysconfig/network";
if(open (NETWORK, "< $file")) {
    while (<NETWORK>) {
        if (/^NISDOMAIN\s*\=\s*(\S+)/) {
            $nisdomain = $1;
        }
    }
    close (NETWORK);
}
### END Got NIS? ###


### BEGIN IP Assignment Method ###
if((!$quiet) and (!$ip_assignment_method))
{
    $satisfied="n";
    $ip_assignment_method="1";
    until($satisfied eq "y")
    {
        system("clear");
        print << 'EOF';
IP Address Assignment
---------------------

There are four ways to assign IP addresses to the client systems on an
ongoing basis:

1) DHCP
   ----------------------------------------------------------------
   A DHCP server will assign IP addresses to clients installed with
   this image.  They may be assigned a different address each time.
   If you want to use DHCP, but must ensure that your clients
   receive the same IP address each time, see "man mkdhcpstatic".

2) STATIC
   ----------------------------------------------------------------
   The IP address the client uses during autoinstall will be
   permanently assigned to that client.

3) REPLICANT
   ----------------------------------------------------------------
   Don't mess with the network settings in this image.  I'm using
   it as a backup and quick restore mechanism for a single machine.

EOF

        print "Which method do you prefer? [$ip_assignment_method]: ";
        $ip_assignment_method=SystemImager::Common->get_response($ip_assignment_method);
        print "You have chosen method $ip_assignment_method for assigning IP addresses.\n";
        print "\nAre you satisfied? ([y]/n): ";
        $satisfied=SystemImager::Common->get_response('y');
	unless(
	           ($ip_assignment_method == "1")
                or ($ip_assignment_method == "2")
                or ($ip_assignment_method == "3")
	      )
              {
                 # reset to default
                 $ip_assignment_method = "1";
                 # send 'em back through the wringer
                 $satisfied="n" ;
              }
    }

# turn number values into useable string values
if   ($ip_assignment_method == "1") { $ip_assignment_method = "dhcp" ; }
elsif($ip_assignment_method == "2") { $ip_assignment_method = "static"      ; }
elsif($ip_assignment_method == "3") { $ip_assignment_method = "replicant"; }
}
### END IP Assignment Method ###


### BEGIN create a fresh master autoinstall script ###
# If not specified, use default for autoinstallscript.conf. -BEF-
unless ($auto_install_script_conf) {
    $auto_install_script_conf = "${imagedir}/etc/systemimager/autoinstallscript.conf";
}

SystemImager::Server->validate_auto_install_script_conf( $auto_install_script_conf );

SystemImager::Server->create_autoinstall_script(
	$script_name,
	$autoinstall_script_dir,
	$config_dir,
	$image,
	$imagedir,
	$ip_assignment_method,
	$post_install,
        $no_listing,
    $auto_install_script_conf
);
### END create a fresh master autoinstall script ###

# prompt to run "addclients"
if (!$quiet) {
    print "Would you like to run the \"addclients\" utility now? (y/[n]): ";
    $continue = SystemImager::Common->get_response('n');
    ($continue ne "n") or exit 0;
    my ($base_host_name, $domain_name) = split(/\./, $golden_client, 2);
    $base_host_name =~ s/\d+$//g;
    my $cmd;
    if ($domain_name) {
        $cmd = "addclients -script $image -host $base_host_name -domainname $domain_name";
    } else {
        $cmd = "addclients -script $image -host $base_host_name";
    }
    exec($cmd);
}



