#!/usr/bin/perl -wT

# =====================================================================
#
# heading     : Configuration
# description : Thin Clients
# navigation  : 7500 7700
#
# =====================================================================
# ===
# === by Trevor Batley, trevor@batley.id.au
# ===
# =====================================================================
# === 
# === Read History in README
# === Versionnumber: 2.0.0-1
# ===
# =====================================================================

package esmith;

use strict;
use CGI ':all';
use CGI::Carp qw(fatalsToBrowser);

use esmith::cgi;
use esmith::ConfigDB;
use esmith::util;
use FileHandle;
use File::Basename;
use File::Path qw(make_path remove_tree);

sub showInitial ($$$$);
sub showStatusReport ($$$$);
sub showFooter ($);
sub showConfigurationPanel ($);
sub showPXEClients ($);
sub showPXEDists ($);
sub loadConfiguration ($);
sub saveClient ($);
sub saveDistribution ($);
sub saveConfiguration ($);
sub showDistributionPanel ($$$);
sub showWorkstationPanel ($$$);
sub checkarchive ($);
sub readini($);

BEGIN
{
    # Clear PATH and related environment variables so that calls to
    # external programs do not cause results to be tainted. See
    # "perlsec" manual page for details.

    $ENV {'PATH'} = '/bin:/usr/bin';
    $ENV {'SHELL'} = '/bin/bash';
    delete $ENV {'ENV'};
}

esmith::util::setRealToEffective ();


my $config = esmith::ConfigDB->open;
my $pxeclients = esmith::ConfigDB->open('thinclient');
my $hosts = esmith::ConfigDB->open_ro('hosts');


my $version = '2.2-1';
my $email = 'trevor@batley.id.au';
my $title = 'Thin Client Configuration';
my $copyright = 'copyright (c) 2004, Trevor Batley, <a href=mailto:'.$email.'>'.$email.'</a>';
my $sendreports = 'Please raise Bugs and Feature Requests in <a target=_blank href=http://bugs.contribs.org/>Bugzilla</a> (SMEContribs => smeserver-thinclient)';

my $thinclientstatus;
my $pxedefaultbase;
my $pxedir;

my @rg_onoffdisplay = ("Enabled", "Disabled");
my @rg_onoffvalue = ("enabled", "disabled");
my %rg_onoffhash =($rg_onoffvalue[0] => $rg_onoffdisplay[0],
                   $rg_onoffvalue[1] => $rg_onoffdisplay[1]);



# ------------------------------------------------------------------------------
# ---
# --- examine state parameter and display the appropriate form
# ---
# ------------------------------------------------------------------------------

my $q = new CGI;

if (! grep (/^state$/, $q->param))
{
  showInitial ($q, '', '', '');
}
elsif ($q->param ('state') eq "configurethinclient")
{
  saveConfiguration ($q); 
}
elsif ($q->param ('state') eq "showDist")
{
  showDistributionPanel ($q, '', '');
}
elsif ($q->param ('state') eq "saveDist")
{
  saveDistribution ($q);
}
elsif ($q->param ('state') eq "showClient")
{
  showWorkstationPanel ($q, '', '');
}
elsif ($q->param ('state') eq "saveClient")
{
  saveClient ($q);
}
else
{
  esmith::cgi::genStateError ($q, $config);
}

exit (0);



# ------------------------------------------------------------------------------
# ---
# --- subroutine to display initial form
# ---
# ------------------------------------------------------------------------------

sub showInitial ($$$$)
{
  my ($q, $status, $msg, $log) = @_;

  $q = new CGI("");
  
  esmith::cgi::genHeaderNonCacheable ($q, $config, $title);
  
  if ($status ne '') {showStatusReport ($q, $status, $msg, $log)};

  showConfigurationPanel ($q);
  showFooter ($q);

  return;
}

# ------------------------------------------------------------------------------
# ---
# --- subroutine to display Status Report
# ---
# ------------------------------------------------------------------------------

sub showStatusReport ($$$$)
{
  my ($q, $status, $msg, $log) = @_;

  my $img_string = qq(src="/server-common/checkmark.jpg" ALT="ERROR");
  if ($status eq "success")
  {
    $img_string = qq(src="/server-common/tickmark.jpg" ALT="SUCCESS");
  }
  print $q->table({-class => "sme-borders"},
        $q->Tr(
            $q->td("<img $img_string>"),
            $q->td($q->div({-class => $status}, 
                $q->h2('Operation Status Report'),
                $q->p($msg))),
        ));

  if ($log ne '')
  {
    open (LOG, $log) || die ("Error while opening temporal log file.\n");
    while (<LOG>)
    {
      print "$_". "<BR>";
    }
    close LOG;
  }

  print $q->hr;

  return;
}

# ------------------------------------------------------------------------------
# ---
# --- subroutine to display Footer Signature
# ---
# ------------------------------------------------------------------------------

sub showFooter ($)
{
  my $q=shift;
  
  print $q->p ($q->hr, $q->font ({size=>"-3"}, $copyright, "<br>", 
                                               $sendreports, "<br>",
                                               "Version ", $version) );
}

# ----------------------------------------------------------------------------
# ---
# ---  subroutine showConfigurationPanel
# ---
# ----------------------------------------------------------------------------

sub showConfigurationPanel($)
{ 
  my ($q) = @_;
   
  print $q->start_multipart_form (-method=>'POST', -action=>$q->url (-absolute=>1));

  # --- PXE Globals
  showPXEGlobals ($q);
  
  # --- PXE Distributions
  print $q->hr;
  showPXEDists ($q);

  # --- PXE Clients
  print $q->hr;
  showPXEClients ($q);

  print $q->hidden (-name=>'state', -override=>1, -default=>'configurethinclient');
  print $q->endform;

  return; 
}

# ----------------------------------------------------------------------------
# ---
# ---  subroutine showPXEGlobals
# ---
# ----------------------------------------------------------------------------

sub showPXEGlobals($)
{ 
  my ($q) = @_;

  # get the pxe and thinclient configuration settings from the configuration database
  my $pxerec = $config->get('pxe');
  my $defaultbase = $pxeclients->get_value('defaultbase');

  # Load display list of available distributions
  my @distrecs = $pxeclients->get_all_by_prop('type' => 'dist');
  my @displaylist = ("default", "None");
  foreach my $distrec (sort @distrecs)
  {
    push (@displaylist, $distrec->key); 
  }

  # Load a list of alternate local hosts for the tftpserver parameter including Self & Other
  my @local = $hosts->get_all_by_prop('HostType'=>'Local');
  my @hostlist = ("Self", "Other");
  foreach my $local (sort @local)
  {
    push (@hostlist, $local->key); 
  }

  # --- Print everything
  print $q->start_table ({-class => "sme-noborders"});
  print $q->p ('PXE Booting is a facility for allowing LAN workstations to boot an operating system ',
               'over the network.<BR>',
               'If PXE Booting is enabled, dhcp will supply the filepath of ',
	       'a pxe bootable image to any LAN workstation requesting it.',
    );

  print $q->p ('You must specifiy which tftp server you will be using to supply the image.<BR>',
               'Selecting <B>Self</B> will activate the tftp server running on this server (if you have installed one - e.g. smeserver-tftp-server).<BR>',
	       'You can also select from any host defined on your network (preferred), or specify ',
	       'the IP address of the machine your tftp server is running on.',
    );

  print $q->p ('The default distribution will be used by all Workstations unless you specify individual ',
               'settings for a Workstation.<BR>',
               'If you select \'None\', you will need to use individual Workstation settings for all Workstations.',
    );

  # --- PXE Boot Status
  my $pxestatus = ($config->get_prop('pxe', 'status') || "disabled");
  print $q->Tr(
    $q->td({-class => "sme-noborders-label"},
      "Your PXE Boot Server is "
      ),
    $q->td({-class => "sme-noborders-content"},
      $q->radio_group (
          -name => 'pxestatus',
          -values => \@rg_onoffvalue,
          -linebreak => 'true',
          -default => $pxestatus,
          -labels => \%rg_onoffhash
        ) 
      ),
    ); 
    
  # --- TFTP Server
  # If using an undefined host set dropdown default to other
  my $ipdefault = ($config->get_prop('pxe', 'nextserver') || "") || "Self";
  my $listdefault = "Other";
  foreach my $host (@hostlist)
  {
    if ($host eq $ipdefault)
    {
      $listdefault = $ipdefault;
      $ipdefault = "";
      last;
    }
  }

  print $q->Tr(
    $q->td({-class => "sme-noborders-label"},
      "Your TFTP Server is "
      ),
    $q->td({-class => "sme-noborders-content"},
      $q->popup_menu (
          -name => 'tftpserver',
          -values => \@hostlist,
          -linebreak => 'true',
          -default => $listdefault),
      ),
    $q->td(" Or "),
    $q->td({class => "sme-noborders-content"},
      $q->textfield (
          -name => 'tftpip',
          -default => $ipdefault, 
	  -overide => 1,
	  -size => 15)),
    ); 
    
  # --- Default Distribution
  print $q->Tr(
    $q->td ({-class => "sme-noborders-label"},
        "The default Distribution is "
      ),
    $q->td({-class => "sme-noborders-content"},
      $q->popup_menu (
        -name => 'pxedist',
        -values => \@displaylist,
        -linebreak => 'true',
        -default => ($defaultbase)), 
      ),
    );
    
  print $q->p ({-class => "sme-noborders-label"},
      esmith::cgi::genButtonRow ($q,
        $q->submit (-name=>'acct', -value=>'Apply'))
    );

  print $q->end_table;  

  # --- TFTP Status (if Self and not turned on issue a warning)
  if (($config->get_prop('pxe', 'nextserver') || "") eq "") # Self
  {
  # --- We only look for tftp (possibly need to check if others exist)
    if ($config->get('tftp'))
    {
      if ($config->get_prop('tftp', 'status') ne 'enabled')
      {
        print $q->p ({-CLASS => "sme-norborders"},
          $q->p ('<B>WARNING:</B> a tftp server is required to supply the boot image to your workstations.<BR> ',
                 'tftp is currently disabled, but will be enabled when you enable PXE Boot Server (you will need to click Apply above).'));
      }
    }
    else
    {
        print $q->p ({-CLASS => "sme-noborders"},
          $q->p ('<B>WARNING:</B> a tftp server is required to supply the boot image to your workstations.<BR> ',
                 'I can\'t find any settings for tftp, so can only assume that you are managing this!'));
    }
  }

  # --- DHCPD Status (if not turned on issue a warning)
  if ($config->get_prop('dhcpd', 'status') ne 'enabled')
    {
    print $q->p ({-CLASS => "sme-norborders"},
      $q->p ('<B>WARNING:</B> all this information is supplied to your workstations via dhcpd.<BR> ',
             'dhcpd is currently disabled, and none of this will work until you enable it!'));
    }

  return; 
}

# ----------------------------------------------------------------------------
# ---
# ---  subroutine showPXEDists
# ---
# ----------------------------------------------------------------------------

sub showPXEDists($)
{ 
  my ($q) = @_;
  my $tftproot = "/tftpboot/";
  if ($config->get('tftp'))
  {
    if ($config->get_prop('tftp', 'status') eq 'enabled') 
    {
	$tftproot = $config->get_prop('tftp', 'tftproot') || "/tftpboot/";
    }
  }

  print $q->h2 ('Distributions');
  print $q->start_table ({-class => "sme-noborders"});

  print $q->p ('The following is a list of the Distributions that you have available for PXE Booting. ',
               'They have either been installed via a package or added manually.',
    );

  my @pxedist = $pxeclients->get_all_by_prop('type' => 'dist');
  if (@pxedist < 1)
  {
    print $q->p ($q->a ({href => $q->url (-absolute => 1) . "?state=showDist&acct=Add"},
                'Click here'),
                'to add your first Thin Client Distribution.');
  }
  else
  {
  #show table
    print $q->p ($q->a ({href => $q->url (-absolute => 1) . "?state=showDist&acct=Add"},
                'Click here'),
                ' to add another Thin Client Distribution. You currently have ' . @pxedist . ' Distribution/s available.');
  }
  print $q->p ;

  #header
  print $q->Tr   (esmith::cgi::genSmallCell ($q, $q->b ('Distribution'), 'header'),
                  esmith::cgi::genSmallCell ($q, $q->b ('Base Directory'), 'header'),
                  esmith::cgi::genSmallCell ($q, $q->b ('Type'), 'header'),
                  $q->td ('&nbsp;'),
                  $q->td ('&nbsp;'));
  
  # default distribution is the gloable PXE parameters
  my $dist = "default";
  my $install = "system";
  print $q->Tr (esmith::cgi::genSmallCell ($q, $dist, 'normal'),
                esmith::cgi::genSmallCell ($q, $tftproot.($config->get_prop('pxe', 'dir') || ""), 'normal'),
	        esmith::cgi::genSmallCell ($q, $install, 'normal'),
    	        esmith::cgi::genCell ($q,
                  $q->a ({href => $q->url (-absolute => 1)
                        . "?state=showDist&acct=Show&dist="
                        . $dist}, 'Show...'), 'normal'));
  
  if (@pxedist)
  {
    # Get sorted list of Distributions - Gotta be a better way!!!
    my @distkeys;
    foreach my $drec (@pxedist)
    {push (@distkeys, $drec->key)};
    foreach my $dist (sort @distkeys)
    {
      my $distrec = $pxeclients->get($dist);
      my $install = $distrec->prop('install') || "Manual";
      if ($install eq "Manual" || $install eq 'Archive') 
      {
        print $q->Tr (esmith::cgi::genSmallCell ($q, $dist, 'normal'),
                      esmith::cgi::genSmallCell ($q, $tftproot.($distrec->prop('dir') || ""), 'normal'),
  		      esmith::cgi::genSmallCell ($q, $install, 'normal'),
    	    	      esmith::cgi::genCell ($q,
            	        $q->a ({href => $q->url (-absolute => 1)
                    		. "?state=showDist&acct=Show&dist="
                    		. $dist}, 'Show...'), 'normal'),
		      esmith::cgi::genCell ($q,
                        $q->a ({href => $q->url (-absolute => 1)
                                . "?state=showDist&acct=Delete&dist="
                                . $dist}, 'Remove...'), 'normal'),
  		      esmith::cgi::genCell ($q,
                        $q->a ({href => $q->url (-absolute => 1)
                                . "?state=showDist&acct=Change&dist="
                                . $dist}, 'Modify...'), 'normal'));
      }
      else
      {
        print $q->Tr (esmith::cgi::genSmallCell ($q, $dist, 'normal'),
                      esmith::cgi::genSmallCell ($q, $tftproot . ($distrec->prop('dir') || ""), 'normal'),
  		      esmith::cgi::genSmallCell ($q, $install, 'normal'),
    	    	      esmith::cgi::genCell ($q,
            	        $q->a ({href => $q->url (-absolute => 1)
                    		. "?state=showDist&acct=Show&dist="
                    		. $dist}, 'Show...'), 'normal'),
		      esmith::cgi::genCell ($q,
                        $q->a ({href => $q->url (-absolute => 1)
                                . "?state=showDist&acct=Delete&dist="
                                . $dist}, 'Remove...'), 'normal'));
      }
    }
  }
  print $q->end_table;
  return; 
}

# ----------------------------------------------------------------------------
# ---
# ---  subroutine showPXEClients
# ---
# ----------------------------------------------------------------------------

sub showPXEClients($)
{ 
  my ($q) = @_;
   
  print $q->h2 ('Individually Controlled Workstations');
  print $q->start_table ({-class => "sme-noborders"});

  print $q->p ('The following is a list of the individual Workstation you have configured for PXE Booting.<BR>',
               'You ONLY need to use this if you want to control an individual Workstations settings independently ',
               'from the default or you don\'t wish to use a default.',
    );

  my @clients = $pxeclients->get_all_by_prop('type' => 'mac');
  if (@clients < 1)
  {
    print $q->p ($q->a ({href => $q->url (-absolute => 1) . "?state=showClient&acct=Add"},
                'Click here'),
                ' to add your first individually controlled Thin Client Workstation.');
  }
  else
  {
  #show table

    print $q->p ($q->a ({href => $q->url (-absolute => 1) . "?state=showClient&acct=Add"},
                'Click here'),
                ' to add another individually controlled Thin Client Workstation.<BR>',
		'You currently have ' . @clients . ' individually controlled  Thin Client Workstation/s defined.');

    #header
    print $q->Tr   (esmith::cgi::genSmallCell ($q, $q->b ('mac address'), 'header'),
                    esmith::cgi::genSmallCell ($q, $q->b ('Name'), 'header'),
                    esmith::cgi::genSmallCell ($q, $q->b ('Distribution'), 'header'),
                    esmith::cgi::genSmallCell ($q, $q->b ('Status'), 'header'),
                    $q->td ('&nbsp;'),
                    $q->td ('&nbsp;'));

    # Get sorted list of Workstations - Gotta be a better way!!!
    my @mackeys;
    foreach my $macrec (@clients)
    {push (@mackeys, $macrec->key)};
    foreach my $client (sort  @mackeys)
    {
      my $clientrec = $pxeclients->get($client);
      print $q->Tr (esmith::cgi::genSmallCell ($q, $client, 'normal'),
                    esmith::cgi::genSmallCell ($q, $clientrec->prop("name") || '', 'normal'),
		    esmith::cgi::genSmallCell ($q, $clientrec->prop("base") || '', 'normal'),
                    esmith::cgi::genSmallCell ($q, $clientrec->prop("status") || '', 'normal'),
                    esmith::cgi::genSmallCell ($q,
                        $q->a ({href => $q->url (-absolute => 1)
                                . "?state=showClient&acct=Delete&mac="
                                . $client}, 'Remove...'), 'normal'),
                    esmith::cgi::genSmallCell ($q,
                        $q->a ({href => $q->url (-absolute => 1)
                                . "?state=showClient&acct=Change&mac="
                                . $client}, 'Modify...'), 'normal'));
    }
    print $q->end_table;
  }
  
  return;
}

# ----------------------------------------------------------------------------
# ---
# ---  subroutine showDistributionPanel
# ---
# ----------------------------------------------------------------------------

sub showDistributionPanel($$$)
{ 
  my ($q, $err, $log) = @_;
  
  my $action = $q->param ('acct');
  my $dist = $q->param ('dist') || "";
  my $dir = $q->param ('dir') || "";
  my $prog = $q->param ('prog') || "pxelinux.0";
  my $ini = $q->param('ini') || "";
  my $origdir = $q->param('origdir') || "";
  my $install = $q->param('install') || "Manual";
  my $tftproot = "/tftpboot/";
  my $arch = "";
  my $aprog = "";
  my $aname = "";
  if ($config->get('tftp'))
  {
    if ($config->get_prop('tftp', 'status') eq 'enabled') 
    {
	$tftproot = $config->get_prop('tftp', 'tftproot') || "/tftpboot/";
    }
  }
  # Get all architectures
  my $pxerecord = $config->get('pxe');
  my %pxearch = $pxerecord->props();
  my @archkeys;
  while (my ($arch, $prog) = each %pxearch)
  {
    unless ($arch eq 'dir' || $arch eq 'default' || $arch eq 'status' || $arch eq 'type' || $arch eq 'nextserver')
    {
	push (@archkeys, $arch);
    }
  }
  
  esmith::cgi::genHeaderNonCacheable ($q, $config, $action . " Distribution");
  
  print $q->start_multipart_form (-method=>'POST', -action=>$q->url (-absolute=>1));

  if ($err ne '') {showStatusReport ($q, 'error', $err, $log)};
  
  # --- Distribution Name
  if ($action eq 'Add')
  {
    print $q->p ('You can add a Distribution via a prebuilt archive.<BR><BR>',
                 'Select the file to load from your local workstation. Minimal checking is done on this file!',
      );
    print $q->p ;
    print $q->start_table ({-class => "sme-noborders"});
    print $q->Tr (
	$q->td ({-class => "sme-noborders-label"}, "Archive:"),
	$q->td ({-class => "sme-noborders-content"},
	    $q->filefield(-name => 'archive',
		          -default => "smeserver-thinclient-<dist>-<version>.noarch.rpm",
			  -size => 32)));

    print $q->end_table;  
    print $q->start_table ({-class => "sme-noborders"});

    print $q->Tr ('Or you can manually add a distribution. You must create the required directories and ',
                  'populate them yourself.<BR>Please read the documentation that comes with the distribution.',
      );

    $dist = "";
    print $q->p ;
    print $q->Tr (
	$q->td ({-class => "sme-noborders-label"}, "Distribution:"),
	$q->td ({-class => "sme-noborders-content"}, $q->textfield (-name => "dist", -override => 1, -default  => $dist, -size => 32)));
    print $q->Tr (
	$q->td ({-class => "sme-noborders-label"}, "Directory:".$tftproot),
	$q->td ({-class => "sme-noborders-content"}, $q->textfield (-name => "dir", -override => 1, -default  => $dir, -size => 32)));
    print $q->Tr (
	$q->td ({-class => "sme-noborders-label"}, "Default Executable:"),
	$q->td ({-class => "sme-noborders-content"}, $q->textfield (-name => "prog", -override => 1, -default  => $prog, -size => 32)));

    # Get sorted list of Architectures and associated executable
    foreach my $arch (sort @archkeys)
    {
      if ($pxeclients->get($arch)) { $aname = $pxeclients->get_prop($arch, 'name') || $arch; } else { $aname = $arch; }
      $aprog = $config->get_prop('pxe', $arch);
      print $q->Tr (
	$q->td ({-class => "sme-noborders-label"}, $aname." Executable:"),
	$q->td ({-class => "sme-noborders-content"}, $q->textfield (-name => $arch, -override => 1, -default  => $aprog, -size => 32)));
    }
    print $q->end_table;  
  }
  elsif ($action eq 'Confirm')
  # Confirm ONLY applies to Distributions from an archive or if they have entered the name of an existing Distribution
  {
    if ($pxeclients->get($dist))
    {
	print $q->p ('<B>'.$dist.'</B> already exists. Do you really want to do this? Or do you want to define a new name and directory below?');
    }
    else
    {
	if ( $dir && -e "$tftproot$dir" )
	{
	    print $q->p ($tftproot.$dir.' already exists. Do you really want to put this here? Or do you want to define a new directory below?');
	}
    }
    if ($ini)
    {
	print $q->p ('These values came from your uploaded archive, you can change them if you want.<BR>',
		     'We will move the content to the new directory, if you change it.'
	    );
    }
    else
    {
	my $message = "Please enter the values you want for this distribution.<BR>";
	if ($install eq "Manual")
	{
	    $message .= "This will just store these parameters in the database.";
	}
	else
	{
	    $message .= "Files from the archive will be installed into the directory you define.";
	}
	print $q->p ($message);
    }
    print $q->p ;
    print $q->start_table ({-class => "sme-noborders"});
    print $q->Tr ($q->td ({-class => "sme-noborders-label"}, "Distribution:"),
	  $q->td ({-class => "sme-noborders-content"},
            $q->textfield (-name => "dist", -override => 1, -default  => $dist, -size => 32)));
    print $q->Tr ($q->td ({-class => "sme-noborders-label"}, "Directory:".$tftproot),
	  $q->td ({-class => "sme-noborders-content"},
            $q->textfield (-name => "dir", -override => 1, -default  => $dir, -size => 32)));
    print $q->Tr (
	$q->td ({-class => "sme-noborders-label"}, "Default Executable:"),
	$q->td ({-class => "sme-noborders-content"}, $q->textfield (-name => "prog", -override => 1, -default  => $prog, -size => 32)));
    # Get sorted list of Architectures and associated executable
    foreach my $arch (sort @archkeys)
    {
      if ($pxeclients->get($arch)) { $aname = $pxeclients->get_prop($arch, 'name') || $arch; } else { $aname = $arch; }
      $aprog = q->param ($arch);
      print $q->Tr (
	$q->td ({-class => "sme-noborders-label"}, $aname." Executable:"),
	$q->td ({-class => "sme-noborders-content"}, $q->textfield (-name => $arch, -override => 1, -default  => $aprog, -size => 32)));
    }
    print $q->end_table;  
    print $q->p ('Press Confirm to install this distribution in the directory defined.');
    print $q->p ;
    print $q->hidden (-name=>'install', -value=>'archive');
  }
  elsif ($action eq 'Show')
  {
  # Show ONLY shows the parameters for a distribution, nothing else....
    if ($dist eq 'default')
    {
	$dir = "";
	$prog = $config->get_prop('pxe', 'default');
	$install = "system";
	print $q->p ('These are the Global PXE parameters for your system. They can\'t be changed from here.');
    }
    else
    {
	$dir = $pxeclients->get_prop($dist, 'dir');
	$prog = $pxeclients->get_prop($dist, 'prog');
	$install = $pxeclients->get_prop($dist, 'install');
	print $q->p ('These are  the parameters for the '.$dist.' Distribution from the database');
    }
    print $q->p;
    print $q->start_table ({-class => "sme-noborders"});
    print $q->Tr(
        $q->td ({-class => "sme-noborders-label"}, "Name: "),
	$q->td({-class => "sme-noborders-content"}, $dist), 
	);
    print $q->Tr(
        $q->td ({-class => "sme-noborders-label"}, "Directory: "),
        $q->td ({-class => "sme-noborders-content"}, $tftproot.$dir), 
        );
    print $q->Tr(
        $q->td ({-class => "sme-noborders-label"}, "Default Executable: "),
        $q->td ({-class => "sme-noborders-content"}, $prog), 
        );
    foreach my $arch (sort @archkeys)
    {
	$aprog;
	if ($pxeclients->get($arch)) { $aname = $pxeclients->get_prop($arch, 'name') || $arch; } else { $aname = $arch; }
	if ($dist eq 'default') 
	{
	    $aprog = $config->get_prop('pxe', $arch);
	}
	else    
	{
	    $aprog = $pxeclients->get_prop($dist, $arch);
	}
	if ($aprog)
	{
	    print $q->Tr (
		$q->td ({-class => "sme-noborders-label"}, $aname." Executable:"),
		$q->td ({-class => "sme-noborders-content"}, $aprog),
		);
	}
    }
    print $q->Tr(
        $q->td ({-class => "sme-noborders-label"}, "Install Type: "),
        $q->td ({-class => "sme-noborders-content"}, $install), 
        );
    print $q->end_table;  
    print $q->p ;
  }
  elsif ($action eq 'Delete')
  {
    # Check to see if any workstations are using the distribution
    my @clients = $pxeclients->get_all_by_prop('base' => $dist);
    if (@clients)
    {
	print $q->p ('<B>WARNING:</B> You have Workstations using the '.$dist.' Distribution. They will not work until you change their configuration.');
    }
    my $distrec = $pxeclients->get($dist);
    if ($dist eq "default")
    {
	$dir = "";
	$prog = $config->get_prop('pxe', 'default');
	$install = "system";
    }
    else
    {
	$dir = $distrec->prop('dir') || "";
	$prog = $distrec->prop('prog') || "pxelinux.0";
	$install = $distrec->prop('install') || "Manual";
    }
    print $q->p ('This will delete the '.$dist.' Distribution from the database');
    print $q->p ;
    print $q->start_table ({-class => "sme-noborders"});
    print $q->Tr(
        $q->td ({-class => "sme-noborders-label"}, "Name: "),
	$q->td({-class => "sme-noborders-content"}, $dist), 
	);
    print $q->Tr(
        $q->td ({-class => "sme-noborders-label"}, "Directory: "),
        $q->td ({-class => "sme-noborders-content"}, $tftproot.$dir), 
        );
    print $q->Tr(
        $q->td ({-class => "sme-noborders-label"}, "Default Executable: "),
        $q->td ({-class => "sme-noborders-content"}, $prog), 
        );
    foreach my $arch (sort @archkeys)
    {
      if ($pxeclients->get($arch)) { $aname = $pxeclients->get_prop($arch, 'name') || $arch; } else { $aname = $arch; }
      $aprog = $distrec->prop($arch);
      if ($aprog)
      {
        print $q->Tr (
	    $q->td ({-class => "sme-noborders-label"}, $aname." Executable:"),
	    $q->td ({-class => "sme-noborders-content"}, $aprog),
	    );
	}
    }
    print $q->end_table;
    if ($dist eq "default")
    {
    	    print $q->p ("You can't delete the Default distribution. These are the global PXE parameters");
    
    }
    elsif ($install eq 'Manual' || $install eq 'Archive')
    {
	if ($dir)
# Don't try to delete the tftp root directory :)
	{
	    print $q->p ('If you want to remove the '.$tftproot.$dir.' directory, and all files, please tick "Delete the '.$tftproot.$dir.' directory and all contents?"');
	}
	else
	{
    	    print $q->p ("You can't delete the $tftproot$dir directory, so you'll have to manipulate the files yourself");
	}
    }
    else
    {
	print $q->p ('This Distribution was installed as an rpm, it will be removed via rpm -e!<BR>',
		    'Which will remove all associated files and database entries.',
	    );
    }
    if ($install eq 'Manual' || $install eq 'Archive')
    {
	if ($dir)
# Don't try to delete the tftp root directory :)
	{
	    print $q->p ("<input type=\"checkbox\" name=\"deldir\" value=\"yes\" />Delete the $tftproot$dir directory and all contents?");
	    print $q->p ;
	}
	print $q->hidden (-name=>'dir', -value=>$dir);
    }
    print $q->hidden (-name=>'dist', -value=>$dist);
    print $q->hidden (-name=>'install', -value=>$install);
  }
  elsif ($action eq 'Change')
  {
    if ($dist eq "default")
    {
	$dir = $config->get_prop('pxe', 'dir') | "";
	$prog = $config->get_prop('pxe', 'default') || "pxelinux.0";
	print $q->p ('This will alter the system wide PXE defaults');
    }
    else
    {
	$dir = $pxeclients->get_prop($dist, 'dir') || "";
	$prog = $pxeclients->get_prop($dist, 'prog') || "pxelinux.0";
	print $q->p ('You can alter the '.$dist.' Distribution parameters in the database');
    }
    if ($dir)
# Don't try to move the tftp root directory :)
    {
        print $q->p ('If you want to move the '.$tftproot.$dir.' directory, and all files,<BR>',
    		    '- please enter the new directory name in the Directory box, and<BR>',
    		    '- tick "Move '.$tftproot.$dir.' and contents?"'
    	    );
    }
    else
    {
        print $q->p ("You can't move the $tftproot$dir directory, but we will move the contents for you");
    }
    print $q->p ;
    print $q->start_table ({-class => "sme-noborders"});
    print $q->Tr (
      esmith::cgi::genNameValueRow ($q, "Name", "dist", $dist),
      );
    print $q->Tr(
      $q->td ({-class => "sme-noborders-label"}, "Directory: "),
      $q->td ({-class => "sme-borders-content"}, "$tftproot<input type=\"text\" name=\"dir\" value=\"$dir\">"), 
      );
    print $q->Tr(
      $q->td ({-class => "sme-noborders-label"}, "Default Executable: "),
      $q->td ({-class => "sme-borders-content"}, "<input type=\"text\" name=\"prog\" value=\"$prog\">"), 
      );
    foreach my $arch (sort @archkeys)
    {
      if ($pxeclients->get($arch)) { $aname = $pxeclients->get_prop($arch, 'name') || $arch; } else { $aname = $arch; }
      # default distribution is the global pxe parameters
      if ($dist eq "default")
      {
	$aprog = $config->get_prop('pxe', $arch);
	print $q->Tr (
	    $q->td ({-class => "sme-noborders-label"}, $aname." Executable:"),
	    $q->td ({-class => "sme-noborders-content"}, "<input type=\"text\" name=\"$arch\" value=\"$aprog\">"),
	    );
      }
      else
      {
	$aprog = $pxeclients->get_prop($dist, $arch);
	print $q->Tr (
	    $q->td ({-class => "sme-noborders-label"}, $aname." Executable:"),
	    $q->td ({-class => "sme-noborders-content"}, "<input type=\"text\" name=\"$arch\" value=\"$aprog\">"),
	    );
      }
    }
    print $q->end_table;
    if ($dir)
# Don't try to move the tftp root directory :)
    {
	print $q->p ({-class => "sme-noborders-content"}, "<input type=\"checkbox\" name=\"movedir\" value=\"yes\">Move $tftproot$dir and contents?");
	print $q->p ;
    }
  }
  else
  {
    showStatusReport ($q, 'error', "We should never get here (Action=$action)", $log)
  }

  if ($action eq 'Show')
  {
    print $q->defaults (-name=>'Return');
  }
  else
  {
    print $q->submit (-name=>'acct', -value=>$action);
    if ($action ne 'Delete') 
    {
	print $q->submit (-name=>'reset', -value=>'Reset');
	print $q->hidden (-name=>'origacct', -value=>$action);
	print $q->hidden (-name=>'origdist', -value=>$dist);
	print $q->hidden (-name=>'origdir', -value=>$dir);
    }
    print $q->defaults (-name=>'Cancel');
  }
  print $q->hidden (-name=>'state', -override=>1, -default=>'saveDist');
  print $q->endform;

  showFooter ($q);

  return;
}

# ----------------------------------------------------------------------------
# ---
# ---  subroutine showWorkstationPanel
# ---
# ----------------------------------------------------------------------------

sub showWorkstationPanel($$$)
{ 
  my ($q, $err, $log) = @_;
   
  my $action = $q->param ('acct');
  my $mac = $q->param ('mac');

  my $name = "";
  my $base = "";
  my $status = "";
  my $distdir = "";
  my $tftproot = "/tftpboot/";
  if ($config->get('tftp'))
  {
    if ($config->get_prop('tftp', 'status') eq 'enabled') 
    {
	$tftproot = $config->get_prop('tftp', 'tftproot') || "/tftpboot/";
    }
  }

  esmith::cgi::genHeaderNonCacheable ($q, $config, $action . " Workstation");
  
  print $q->start_multipart_form (-method=>'POST', -action=>$q->url (-absolute=>1));

  if ($err ne '')
  {
    showStatusReport ($q, 'error', $err, $log);
    $name = $q->param ('name');
    $base = $q->param ('base');
    $status = $q->param ('status');
  }
  
  print $q->start_table ({-class => "sme-noborders"});

  if ($action eq "Add")
  {
    if ($err eq '')
    {
      $mac = "";
      $name = "";
      $base = ($pxeclients->get_value('defaultbase') || "");
      $status = "enabled";
    }

    print $q->p ('These settings over-ride the global parameters for this Workstation ONLY!',);

    # Can ONLY enter the mac address on add
    print $q->Tr ({-CLASS => "sme-noborders"},
      esmith::cgi::genNameValueRow ($q, "Device Address", "mac", $mac)
      ),"\n";
  }
  else
  {
    my $clientrec = $pxeclients->get($mac);

    if ($err eq '')
    {
      $name = $clientrec->prop('name');
      $base = $clientrec->prop('base') || "";
      $status = $clientrec->prop('status') || "enabled";
    }

    if ($action eq "Delete")
    {
        print $q->p ('Deleting these settings will return this Workstation to using the global parameters.',);
    }
    else
    {
        print $q->p ('These settings over-ride the global parameters for this Workstation ONLY!',);
    }

    # Can't change the mac address on change or delete
    print $q->Tr(
	$q->td ({-class => "sme-noborders-label"},
	    "Device Address: "
	    ),
	$q->td ({-class => "sme-noborders-content"},
	    $mac
	    ), 
	);
    print $q->hidden (-name=>'mac', -value=>$mac);
  }
  
  $distdir = $pxeclients->get_prop($base, 'dir') || "";

  if ($action eq 'Delete')
  {
    # --- Just display the stuff for confirmation of the delete
    print $q->Tr(
	$q->td ({-class => "sme-noborders-label"},
	    "Device Name: "
	    ),
	$q->td ({-class => "sme-noborders-content"},
	    $name
	    ), 
	);
    print $q->Tr(
	$q->td ({-class => "sme-noborders-label"},
	    "The Distribution used is: "
	    ),
	$q->td ({-class => "sme-noborders-content"},
	    "$base \($tftproot$distdir\)"
	    ), 
	);
    print $q->Tr(
	$q->td ({-class => "sme-noborders-label"},
	    "Status: "
	    ),
	$q->td ({-class => "sme-noborders-content"},
	    $status
	    ), 
	);
    print $q->hidden (-name=>'name', -value=>$name);
    print $q->hidden (-name=>'base', -value=>$base);
    print $q->hidden (-name=>'status', -value=>$status);
  }
  else
  {
    # Allow alteration of all fields    
    print $q->Tr ({-CLASS => "sme-noborders"},
      esmith::cgi::genNameValueRow ($q, "Device Name", "name", $name)
    );

    # Load display list of available distributions
    my @distrecs = $pxeclients->get_all_by_prop('type' => 'dist');
    my @displaylist;
    foreach my $distrec (@distrecs)
    {
      push (@displaylist, $distrec->key); 
    }
    
    print $q->Tr(
      $q->td ({-class => "sme-noborders-label"},
          "The Distribution used is: "
      ),
      $q->td({-class => "sme-noborders-content"},
        $q->popup_menu (
          -name => 'base',
          -values => \@displaylist,
          -linebreak => 'true',
          -default => $base
        ) 
      ),
      $q->td ({-class => "sme-noborders"},
          " \(" . $tftproot . $distdir . "\)"
      ),
    );
    print $q->Tr(
      $q->td({-class => "sme-noborders-label"},
        "Device Status: "
      ),
      $q->td({-class => "sme-noborders-content"},
        $q->radio_group (
            -name => 'status',
            -values => \@rg_onoffvalue,
            -linebreak => 'true',
            -default => $status,
            -labels => \%rg_onoffhash
        ),
	$q->td ('these parameters ONLY! Global parameters will apply if individual parameters are disabled'),
      ),
    ); 
  }

  print $q->end_table,"\n";  

  print $q->submit (-name=>'acct', -value=>$action);
  if ($action ne 'Delete') 
  {
    print $q->submit (-name=>'reset', -value=>'Reset');
    print $q->hidden (-name=>'origacct', -value=>$action);
    print $q->hidden (-name=>'origmac', -value=>$mac);
  }
  print $q->defaults (-name=>'Cancel');
    

  print $q->hidden (-name=>'state', -override=>1, -default=>'saveClient');
  print $q->endform;

  showFooter ($q);

  return;
}

# ------------------------------------------------------------------------------
# ---
# --- subroutine Save Configuration
# ---
# ------------------------------------------------------------------------------

sub saveConfiguration ($)
{
  my ($q) = @_;

  my $pxestatus = $q->param('pxestatus');
  my $tftpserver = $q->param('tftpserver');
  my $pxedefaultbase = $q->param('pxedist');

  my $tftpip = "";
  my $nextserver = "";
  my $pxerec = $config->get('pxe');
#  if ((($tftpserver eq $pxerec->prop('nextserver')) || ($tftpserver eq "Other")) && $q->param('tftpip'))
  if ($tftpserver eq "Other")
  {
    if ($q->param('tftpip'))
    {
	$tftpip = $q->param('tftpip');
	my $err = checkip($tftpip);
	if ($err eq "OK")
	{
    	    $nextserver = $tftpip;
	}
	else
	{
    	    showInitial ($q, "error", "Incorrect IP [$tftpip], $err, Update unsuccessfull", '');
    	    return;
	}
    }
    else # Can only get this if Other is selected & no IP entered
    {
	showInitial ($q, "error", "You must enter an IP if you select Other, Update unsuccessfull", '');
	return;
    }
  }
  elsif ($tftpserver ne "Self")
  {
    $nextserver = $hosts->get_prop($tftpserver, 'InternalIP');
#    $nextserver = $tftpserver;
  }
  
  $pxerec->set_prop('status' => $pxestatus);
  if ($nextserver) { $pxerec->set_prop('nextserver' => $nextserver); } else { $pxerec->delete_prop('nextserver'); }
  $pxeclients->set_value('defaultbase' => $pxedefaultbase);
  
  # What else needs to be activated if we are enabling PXE Booting?
  # Turn on/off the bootp=allow parameter in the dhcpd configuration item (don't care if it's activated ATM)
  my $dhcprec = $config->get('dhcpd');
  if ($pxestatus eq "enabled")
  {
    $dhcprec->set_prop('Bootp' => "allow");
  }
  else
  {
    $dhcprec->set_prop('Bootp' => "deny");
  }

  # If we are using the tftp server locally, enable &/or disable in alignment with pxe
  if ($tftpserver eq "Self") 
  {
    if ($config->get('tftp'))
    {
      my $tftprec = $config->get('tftp');
      $tftprec->set_prop('status' => $pxestatus);
    }
  }

  if (system ("/sbin/e-smith/signal-event smeserver-thinclient-update > /var/log/thinclient.log 2>&1"))
  {
    showInitial($q, "error", "Error occurred during smeserver-thinclient-update event.", "/var/log/thinclient.log");
    return;
  }
  # If we are using the tftp server locally trigger a tftp reconfiguration
  if ($tftpserver eq "Self") 
  {
    if (system ("/sbin/e-smith/signal-event smeserver-tftp-server-update > /var/log/thinclient.log 2>&1"))
    {
      showInitial($q, "error", "Error occurred during smeserver-tftp-server-update event.", "/var/log/thinclient.log");
      return;
    }
  }

  showInitial ($q, "success", "ThinClient parameters successfully updated.", "");

  return;
}

# ----------------------------------------------------------------------------
# ---
# ---  subroutine saveClient
# ---
# ----------------------------------------------------------------------------

sub saveClient ($)
{
  my ($q) = @_; 

  if ($q->param('reset'))
  {
    my $origacct = $q->param('origacct');
    my $origmac = $q->param('origmac');
    $q = new CGI("");
    $q->param(-name=>'acct', -value=>$origacct);
    $q->param(-name=>'mac', -value=>$origmac);
    showWorkstationPanel ($q, '', '');
    return;
  }

  my $action = $q->param('acct');
  my $mac = $q->param('mac');
  my $macrec;
  
  if ($action eq "Delete")
  {
    $macrec = $pxeclients->get($mac);
    $macrec->delete;
  }
  else
  {
    my $name = $q->param('name');
    # If no name entered use mac address (without :'s)
    if ($name eq "")
    {
	$name = $mac;
	$name =~ s/://g;
    }
    # Validate the name does NOT contain spaces - dhcpd.conf can't handle this
    if ($name =~ / /gi)
    {
      showWorkstationPanel ($q, "Workstation name [$name], cannot contain spaces. $action unsuccessfull", "");
      return;
    }

    my $base = $q->param('base');
    my $status = $q->param('status');
    if ($action eq "Add")
    {

      # Validate the mac address
      my $err = checkmac($mac);
      if ($err ne "OK")
      {
        showWorkstationPanel ($q, "Incorrect mac [$mac], $err. $action unsuccessfull", "");
        return;
      }

      # All OK, add the details
      my %newvalues = ('type' => 'mac',
                       'name' => $name,
		       'base' => $base,
		       'status' => $status);
      $macrec = $pxeclients->new_record($mac, \%newvalues);
    }
    else
    {
      # All OK, update the details
      $macrec = $pxeclients->get($mac);
      $macrec->set_prop('name' => $name);
      $macrec->set_prop('base' => $base);
      $macrec->set_prop('status' => $status);
    }
  }
  
  if (system ("/sbin/e-smith/signal-event smeserver-thinclient-update > /var/log/thinclient.log 2>&1"))
  {
    showWorkstationPanel($q, "Error occurred during thinclient-update event.", "/var/log/thinclient.log");
    return;
  }

  showInitial ($q, "success", "$action of Workstation [$mac], successfull", "");

  return;
}

# ----------------------------------------------------------------------------
# ---
# ---  subroutine saveDistribution
# ---
# ----------------------------------------------------------------------------

sub saveDistribution ($)
{
  my ($q) = @_; 
  my $action = $q->param('acct');
  my $dist = $q->param('dist');
  my $dir = $q->param('dir');
  my $prog = $q->param('prog');
  my $archive = $q->param('archive');
  my $ext = $q->param('ext') || "";
  my $deldir = $q->param('deldir');
  my $movedir = $q->param('movedir');
  my $install = $q->param('install') || "Manual";
  my $origacct = $q->param('origacct');
  my $origdist = $q->param('origdist');
  my $origdir = $q->param('origdir');
  my $safe_filename_characters = "a-zA-Z0-9_.-";
  my $tftproot = $config->get_prop('tftp', 'tftproot') || "/tftpboot/";
  my $ini = "";
  my $arch = "";
  my $aprog = "";
  # Get all architectures
  my $pxeconfig = $config->get('pxe');
  my %pxearch = $pxeconfig->props;
  my @archkeys;
  while (my ($arch, $prog) = each %pxearch)
  {
    unless ($arch eq 'dir' || $arch eq 'default' || $arch eq 'status' || $arch eq 'type' || $arch eq 'nextserver')
    {
	push (@archkeys, $arch);
    }
  }
  my %archs;
  foreach my $arch (@archkeys)
  {
    my $aprog = $q->param($arch);
    $archs {$arch} = $aprog;
  }
  if ($q->param('reset'))
  {
    $q = new CGI("");
    $q->param(-name=>'acct', -value=>$origacct);
    $q->param(-name=>'dist', -value=>$origdist);
    $q->param(-name=>'dir', -value=>$origdir);
    foreach my $arch (@archkeys)
    {
	my $aprog = $config-get_prop($arch, 'prog');
	$q->param(-name=>$arch, -value=>$aprog);
    }
    showDistributionPanel ($q, '', '');
    return;
  }

  if ($action eq 'Add')
  {
    if ($archive)
    {
# If we are adding via an archive:
# - upload the archive
# - extract the archive
# - check if it has a thinclient.ini file and load defaults if it does
	$install = "Archive";
	# Untaint the filename
	if ( $archive =~ /^([$safe_filename_characters]+)$/ ) 
	{ 
	    $archive = $1;
	}
	else 
	{ 
	    die "Filename contains invalid characters";
	}
	# Upload the file
	my $fharchive = $q->param('archive');
    	remove_tree ("/tmp/$archive");
	open (WR,">/tmp/$archive") || die ("Error while opening temporally file.\n");
	binmode WR;
	while ( <$fharchive> )
	{
	    print WR;
	}
	close WR;
	# Extract the archive
	my ( $name, $path, $ext ) = fileparse ( $archive, qr/\.[^.]*/ );
	my $unzip = "/bin/tar -xf /tmp/$archive -C /tmp/thinclient";
	if ($ext eq ".rpm")
	{
	    $unzip = "/bin/rpm -Uvh /tmp/$archive";
	    $install = $archive;
	}
	elsif ($ext eq ".zip")
	{
	    $unzip = "/usr/bin/unzip /tmp/$archive -d /tmp/thinclient";
	}
	else
	{
	    $unzip = "/bin/tar -xf /tmp/$archive -C /tmp/thinclient";
	}
	remove_tree ("/tmp/thinclient");
	mkdir "/tmp/thinclient";
	if (system ($unzip." > /var/log/thinclient.log 2>&1"))
	{
	    showDistributionPanel($q, "Error occurred during archive extract.(ext=$ext)", "/var/log/thinclient.log");
	    return;
	}
	# Now that we have unpacked it, delete the uploaded archive
	unlink "/tmp/$archive";
	# If there is a thinclient.ini within the archive, use the parameters in it
	if (-e "/tmp/thinclient/thinclient.ini")
	{
	    my $cfg = readini( "/tmp/thinclient/thinclient.ini" );
	    unlink "/tmp/thinclient/thinclient.ini";
    	    $dist = $cfg->{"params"}->{"dist"};
	    $dir = $cfg->{"params"}->{"dir"};
	    $prog = $cfg->{"params"}->{"prog"} || "pxelinux.0";
	    $ini = "yes";
	}
	if ($ext ne ".rpm")
# rpms do everything themselves (db params & install)
# We ask for confirmation of parameters for all other archive types
	{
	    $q = new CGI("");
	    $q->param(-name=>'acct', -value=>"Confirm");
	    $q->param(-name=>'dist', -value=>$dist);
	    $q->param(-name=>'dir', -value=>$dir);
	    $q->param(-name=>'prog', -value=>$prog);
	    while (($arch, $aprog) = each (%archs))
	    {
		$q->param(-name=>$arch, -value=>$aprog);
	    }
	    $q->param(-name=>'install', -value=>$install);
	    $q->param(-name=>'ini', -value=>$ini);
	    showDistributionPanel ($q, '', '');
	    return;
	}
    }
    else
    {
# It must be a Manual entry
	if ($pxeclients->get($dist))
	{
	    $q = new CGI("");
	    $q->param(-name=>'acct', -value=>"Confirm");
	    $q->param(-name=>'dist', -value=>$dist);
	    $q->param(-name=>'dir', -value=>$dir);
	    $q->param(-name=>'prog', -value=>$prog);
	    while (($arch, $aprog) = each (%archs))
	    {
		$q->param(-name=>$arch, -value=>$aprog);
	    }
	    $q->param(-name=>'install', -value=>"Manual");
	    showDistributionPanel ($q, '', '');
	    return;
	}
	my %newvalues = ('type' => 'dist',
	                 'dir' => $dir,
		         'prog' => $prog,
		         'install' => 'Manual');
        while (($arch, $aprog) = each (%archs))
        {
	    $newvalues{$arch} = $aprog;
	}
	if ( $dir =~ /^([$safe_filename_characters]+)$/ ) 
	{	
	    $dir = $1; 
	}
	else 
	{ 
	    die "Directory contains invalid characters\n"; 
	}
	my $owner = $config->get_prop('tftp', 'user') || "nobody";
	unless (-d "$tftproot$dir") {make_path "$tftproot$dir", {owner=>$owner, group=>$owner};}
	my $distrecord = $pxeclients->new_record($dist, \%newvalues);
    }
  }
  elsif ($action eq 'Confirm')
  {
# We ONLY get to Confirm, if it's an archive install (non rpm)
# Move the contents of the archive into the specified directory
# and update the config
    if ( $dist eq "" )
    {
        showDistributionPanel ($q, "You MUST define a Distribution name.", '');
        return;
    }	
    if ( $dir =~ /^([$safe_filename_characters]+)$/ ) 
    {	
	$dir = $1; 
    }
    else 
    { 
	die "Directory contains invalid characters\n"; 
    }
    rename ("/tmp/thinclient", "$tftproot$dir");
    my %newvalues = ('type' => 'dist',
	             'dir' => $dir,
		     'prog' => $prog,
		     'install' => 'Archive');
    while (($arch, $aprog) = each (%archs))
    {
	$newvalues{$arch} = $aprog;
    }
    my $distrecord = $pxeclients->new_record($dist, \%newvalues);
  }
  elsif ($action eq "Delete")
  {
    if ($install eq "Manual" || $install eq "Archive")
    {
        my $distrecord = $pxeclients->get($dist);
        $distrecord->delete;
	# If they ticked the delete directory box, delete it
	if ( $dir =~ /^([$safe_filename_characters]+)$/ ) 
	{ 
	    $dir = $1; 
	}
	else 
	{ 
	    die "Filename contains invalid characters"; 
	}
        if ($deldir eq 'yes')
        {
	    remove_tree ("$tftproot$dir");
	}
    }
    else
    # We have to assume it's via rpm
    {
	my $rpm = $pxeclients->get_prop($dist, 'install');
        if (system ("/bin/rpm -e $rpm > /var/log/thinclient.log 2>&1"))
        {
          showDistributionPanel($q, "Error occurred during rpm uninstall.", "/var/log/thinclient.log");
          return;
        }
    }
  }
  elsif ($action eq "Change")
  {
    if ($dist eq "default")
    # Changes to the default are changes to the global PXE configuration
    {
	if ($dir) { $pxeconfig->set_prop('dir' => $dir );} else { $pxeconfig->delete_prop('dir'); };
	$pxeconfig->set_prop('default' => $prog);
	while (($arch, $aprog) = each (%archs))
	{
	    $pxeconfig->set_prop($arch => $aprog);
	}
    }
    elsif ($install eq "Manual" || $install eq "Archive")
    {
        if ($dist eq $origdist)
        # If the Distribution name hasn't changed, just update the fields
        {
    	    my $distrecord = $pxeclients->get($origdist);
    	    $distrecord->set_prop('dir' => $dir);
    	    $distrecord->set_prop('prog' => $prog);
	    while (($arch, $aprog) = each (%archs))
	    {
	        $distrecord->set_prop($arch => $aprog);
	    }
        }
        else
        # If the Distribution name has changed, create the new one and delete the old
        {
	    my %newvalues = ('type' => 'dist',
		             'dir' => $dir,
			     'prog' => $prog,
			      'install' => $install);
	    while (($arch, $aprog) = each (%archs))
	    {
	        $newvalues{$arch} = $aprog;
	    }
	    my $distrecord = $pxeclients->new_record($dist, \%newvalues);
    	    my $distrecord = $pxeclients->get($origdist);
    	    $distrecord->delete;
        }
        # If the directory has changed and we selected move directory
        if ($dir ne $origdir && $movedir eq 'yes')
        {
	    if ( $origdir =~ /^([$safe_filename_characters]+)$/ ) 
	    { 
		$origdir = $1; 
	    }
	    else 
	    { 
		die "Filename $origdir contains invalid characters"; 
	    }
	    if ( $dir =~ /^([$safe_filename_characters]+)$/ ) 
	    { 
		$dir = $1; 
	    }
	    else 
	    { 
		die "Filename $dir contains invalid characters"; 
	    }
	    rename ("$tftproot$origdir", "$tftproot$dir");
	}
    }
    else
    {
        showDistributionPanel ($q, "This Distribution was installed via rpm. You can\'t modify these settings.", '');
        return;
    }
  }
  else
  {
      showDistributionPanel ($q, "$action, Oops!", '');
      return;
  }

  if (system ("/sbin/e-smith/signal-event smeserver-thinclient-update > /var/log/thinclient.log 2>&1"))
  {
    showInitial($q, "error", "Error occurred during thinclient-update event.", "/var/log/thinclient.log");
    return;
  }
  
  showInitial ($q, "success", "$action of Distribution $dist, successfull", "");
  return;
  
}

# ----------------------------------------------------------------------------
# ---
# ---  subroutine checkmac
# ---
# ----------------------------------------------------------------------------

sub checkmac($)
{
  my ($mac) = @_;
  $_ = lc $mac;  # easier to match on $_
  
if (not defined $_) 
    {
        return "You must provide a MAC address";
    } 
    elsif (/^([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f]){5})$/) 
    {
      my @macexists = $hosts->get_all_by_prop('MACAddress'=>$mac);
      if (@macexists > 0)
      {
        return "MAC address already assigned as a Local host";
      }
      elsif ($pxeclients->get($_))
      {
        return "MAC address already assigned as a Workstation";
      }
      else
      {
        return "OK";
      }
    }
    else 
    {
        return "The MAC address you provided was not valid";
    }
}

# ----------------------------------------------------------------------------
# ---
# ---  subroutine checkip
# ---
# ----------------------------------------------------------------------------

sub checkip($)
  {
    my ($ip) = @_;

    return undef unless defined $ip;

    return 'Doesn\'t look like an IP' unless $ip =~ /^[\d.]+$/;

    my @octets = split /\./, $ip;

    return 'Not enough octets (expected X.X.X.X)' unless scalar @octets == 4;

    foreach my $octet (@octets) 
    {
        return "$octet is more than 255" if $octet > 255;
    }

    return 'OK';
  }

# ----------------------------------------------------------------------------
# ---
# ---  subroutine checkarchive
# ---  check the archive type and determine appropriate command to extract
# ---
# ----------------------------------------------------------------------------

sub checkarchive($)
    {
    my ($archive) = @_;
  
    return undef unless defined $archive;

    my ($ext) = $archive =~ /((\.[^.\s]+)+)$/;
    if ($ext eq "tar.gz" || $ext eq "tgz" || $ext eq "tar.bz2" || $ext eq "tbz2" )
    {
	return "/bin/tar -xf /tmp/$archive -C /tmp/thinclient/", $ext;
    
    } 
    elsif ($ext eq "zip")
    {
	return "/usr/bin/unzip /tmp/$archive -d /tmp/thinclient/", $ext;
    }
    elsif ($ext eq "rpm")
    {
	return "/bin/rpm -Uvh /tmp/$archive", $ext;
    }
    else
    {
	# We'll try tar and hope it understands the extension.....
	return "/bin/tar -xf /tmp/$archive -C /tmp/thinclient/", $ext;
    }
    }
    
# ----------------------------------------------------------------------------
# ---
# ---  subroutine read an ini file
# ---
# ----------------------------------------------------------------------------

sub readini($)
  {
  my ($ini) = @_;
  my $cfg;
  my $section;
  open (INI, "$ini") || die "Can't open $ini: $!\n";
  while (<INI>) {
    chomp;
    if ( /^\s*\[\s*(.+?)\s*\]\s*$/ )
    {
	$section = $1;
    }
    if ( /^\s*([^=]+?)\s*=\s*(.*?)\s*$/ )
    {
	$cfg->{$section}->{$1} = $2;
    }
    }
    close (INI);
    return $cfg;
  }
 