#!/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.124 2002/11/04 16:05:25 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>
#

# 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 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

Copyright (C) 1999-2001 Brian Elliott Finley <brian.finley\@baldguysoftware.com>
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
EOF

$get_help = "          Try \"$program_name -help\" for more options.";
$help_info = $version_info . <<"EOF";

Usage: $program_name [OPTION]...  -golden-client HOSTNAME -image IMAGENAME

Options: (options can be presented in any order and may be abbreviated)

 -help                    Display this output.

 -version                 Display version and copyright information.

 -golden-client HOSTNAME  Hostname or IP address of the \"golden\" client.

 -image IMAGENAME         Where IMAGENAME is the name to assign to the 
                          image you are retrieving.  This can be either
                          the name of a new image if you want to create
                          a new image, or the name of an exisiting image
                          if you want to update an image.  

 -ssh-user USERNAME       Username for ssh connection to the client.
                          Only needed if a secure connection is required.

 -log "STRING"		  Quoted string for log file format.  See the
			  rsyncd.conf man page for options.

 -quiet                   Don\'t ask any questions or print any output
                          (other than errors). In this mode, no warning
                          will be given if the image already exists on
                          the server.

 -directory PATH          The full path and directory name where you want
                          this image to be stored.  The directory bearing
                          the image name itself will be placed inside the
                          directory specified here.

 -exclude PATH            Don\'t pull the contents of PATH from the 
                          golden client.  PATH must be absolute (starting 
			  with a "/").  
			  
			  To exclude a single file use:
			   -exclude /directoryname/filename

                          To exclude a directory and it's contents use:
                           -exclude /directoryname/

			  To exclude the contents of a directory, but
			  pull the directory itself use:
			   -exclude "/directoryname/*"

 -exclude-file FILE       Don\'t pull the PATHs specified in FILE from the
                          golden client.

 -update-script [YES|NO]  Update the \$image.master script?  Defaults to 
                          NO if -quiet.  If not specified you will be
                          prompted to confirm an update.

 -no-listing              Don't show each filename as it is copied over during
                          install.  This is useful for times when your console
                          device is slow (e.g. serial console), and is the 
                          bottleneck of your installation.

The following options affect the autoinstall client after autoinstalling:

 -ip-assignment METHOD    Where METHOD can be DHCP, STATIC, or REPLICANT.

                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".

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

                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.


 -post-install ACTION     Where ACTION can be BEEP, REBOOT, or SHUTDOWN.

                BEEP 
                ----------------------------------------------------------------
                Clients will beep incessantly after succussful completion of an
                autoinstall.  (default)

                REBOOT 
                ----------------------------------------------------------------
                Clients will reboot themselves after successful completion of 
                an autoinstall.

                SHUTDOWN 
                ----------------------------------------------------------------
                Clients will halt themselves after successful completion of an 
                autoinstall.


Download, report bugs, and make suggestions at:
http://systemimager.org/
EOF
    

my $golden_client;
my $imageserver;
my $ip_assignment_method;
my $image;
my $exclude;
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"         => \$exclude,
    "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 $exclude is an absolute path starting with /
if($exclude){
  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 ($exclude 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, or reiserfs.

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

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 exisiting 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($exclude) { 
      print FINAL_EXCLUDE_FILE "\n# Exclusions from -exclude on the command line.\n";
      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 -av --numeric-ids $options |");
    print "------------- $image IMAGE RETRIEVAL PROGRESS -------------\n";
    while (<RSYNC>) {
        print $_;
    }
    print "------------- $image IMAGE RETRIEVAL FINISHED -------------\n";
    close (RSYNC);
} else {
    $command = qq(rsync -a --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 ###


### 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);
}



