#!/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, '.$email.''; my $sendreports = 'Please raise Bugs and Feature Requests in Bugzilla (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(""), $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 () { print "$_". "
"; } 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, "
", $sendreports, "
", "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.
', '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.
', 'Selecting Self will activate the tftp server running on this server (if you have installed one - e.g. smeserver-tftp-server).
', '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.
', '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 ('WARNING: a tftp server is required to supply the boot image to your workstations.
', '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 ('WARNING: a tftp server is required to supply the boot image to your workstations.
', '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 ('WARNING: all this information is supplied to your workstations via dhcpd.
', '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 (' '), $q->td (' ')); # 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.
', '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.
', '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 (' '), $q->td (' ')); # 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.

', '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--.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.
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 (''.$dist.' 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.
', '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.
"; 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 ('WARNING: 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!
', '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 ("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,
', '- please enter the new directory name in the Directory box, and
', '- 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"), ); print $q->Tr( $q->td ({-class => "sme-noborders-label"}, "Default Executable: "), $q->td ({-class => "sme-borders-content"}, ""), ); 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"}, ""), ); } else { $aprog = $pxeclients->get_prop($dist, $arch); print $q->Tr ( $q->td ({-class => "sme-noborders-label"}, $aname." Executable:"), $q->td ({-class => "sme-noborders-content"}, ""), ); } } print $q->end_table; if ($dir) # Don't try to move the tftp root directory :) { print $q->p ({-class => "sme-noborders-content"}, "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 () { chomp; if ( /^\s*\[\s*(.+?)\s*\]\s*$/ ) { $section = $1; } if ( /^\s*([^=]+?)\s*=\s*(.*?)\s*$/ ) { $cfg->{$section}->{$1} = $2; } } close (INI); return $cfg; }