summaryrefslogblamecommitdiffstats
path: root/src/boot-env/OpenSLX/BootEnvironment/PXE.pm
blob: e48c7a1ad3ad471e9b05a593ef1a529385df7650 (plain) (tree)
1
                                         




















                                                                               
                            
                
                 



                    









                                                                            

                                                  








                                                       






                                 



                                                       



                                                          
                                                                      
                                                          

                                                  
                               
                                      









                                                                            
                                    
     
                                                          



                                                                           
                                                           

                                                  

                              
                                        



                                                                                





                                                               





                                                               
         
                                                                               
                                                            
                                                               
                                    
                                                  
                                  
                                                           
                                                                       
                                           
                                       

                                                              


                                                          







                                                                     
                                             





































                                                                               
                                                


                                          




                                                                           

                                                                             
                                                                               



                                                                            



                                                                          

                                                                        






                     
 
                                                              
 

                                                   
                                                      
 








                                                                         




                                                   





                                                                              


                                                                   
                                                             
                      
                                                                 







                                              
                                  



                                                                                
 
                                       
            



                                                                       

                       
                                                 
                                                       
                                                                       

         




                                                                              

                                                                   



                                                                     
                                                                            





                                                                           




                                           






                                                    
                                                   




                               
                                                                               
                                                                   
                                       
                          
                                                                                 
                  

             
                              
                                                          




                                               



                                                                   
                                                  


              






                                                
                                               




















                                                                          
                                                   



                                              
  
# Copyright (c) 2008..2009 - OpenSLX GmbH
#
# This program is free software distributed under the GPL version 2.
# See http://openslx.org/COPYING
#
# If you have any feedback please consult http://openslx.org/feedback and
# send your suggestions, praise, or complaints to feedback@openslx.org
#
# General information about OpenSLX can be found at http://openslx.org/
# -----------------------------------------------------------------------------
# BootEnvironment::PXE.pm
#    - provides PXE-specific implementation of the BootEnvironment API.
# -----------------------------------------------------------------------------
package OpenSLX::BootEnvironment::PXE;

use strict;
use warnings;

use base qw(OpenSLX::BootEnvironment::Base);

use File::Basename;
use File::Path;
# for sha1 passwd encryption
use Digest::SHA;
use MIME::Base64;

use OpenSLX::Basics;
use OpenSLX::Utils;

sub initialize
{
    my $self   = shift;
    my $params = shift;
    
    return if !$self->SUPER::initialize($params);

    $self->{'original-path'} = "$openslxConfig{'public-path'}/tftpboot";
    $self->{'target-path'}   = "$openslxConfig{'public-path'}/tftpboot.new";

    $self->{'requires-default-client-config'} = 1;

    if (!$self->{'dry-run'}) {
        mkpath([$self->{'original-path'}]);
        rmtree($self->{'target-path'});
        mkpath("$self->{'target-path'}/client-config");
    }

    return 1;
}

sub writeBootloaderMenuFor
{
    my $self             = shift;
    my $client           = shift;
    my $externalClientID = shift;
    my $systemInfos      = shift;

    $self->_prepareBootloaderConfigFolder() 
        unless $self->{preparedBootloaderConfigFolder};

    my $pxePath       = $self->{'target-path'};
    my $pxeConfigPath = "$pxePath/pxelinux.cfg";

    my $pxeConfig    = $self->_getTemplate();
    my $pxeFile      = "$pxeConfigPath/$externalClientID";
    my $clientAppend = $client->{attrs}->{kernel_params_client} || '';
    my $bootURI      = $client->{attrs}->{boot_uri} || '';
    vlog(1, _tr("writing PXE-file %s", $pxeFile));

    # set label for each system
    foreach my $info (@$systemInfos) {
        my $label = $info->{label} || '';
        if (!length($label) || $label eq $info->{name}) {
            if ($info->{name} =~ m{^(.+)::(.+)$}) {
                my $system = $1;
                my $exportType = $2;
                $label = $system . ' ' x (40-length($system)) . $exportType;
            } else {
                $label = $info->{name};
            }
        }
        $info->{menuLabel} = $label;
    }
#   if kernel=*xen* then run sub _xenLabel from xen.pm    
    my $slxLabels = '';
    foreach my $info (sort { $a->{label} cmp $b->{label} } @$systemInfos) {
        my $vendorOSName = $info->{'vendor-os'}->{name};
        my $kernelName   = basename($info->{'kernel-file'});
        my $append       = $info->{attrs}->{kernel_params};
        my $pxeLabel     = $info->{'external-id'};
        $pxeLabel        =~ s/::/-/g;  
        my $pxePrefix    = '';
        my $tftpPrefix   = '';
        $info->{'pxe_prefix_ip'} ||= '';
        
        # pxe_prefix_ip set and looks like a ip
        if ($info->{'pxe_prefix_ip'} =~ m/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/) {
        	$pxePrefix = "$info->{'pxe_prefix_ip'}::";
        	$tftpPrefix = "tftp://$info->{'pxe_prefix_ip'}"
        	   if ! length($bootURI);
        }
        
        # set default menu entry
        my $pxeDefault = "";
        if (defined $openslxConfig{'pxe-default-menu-entry'}) {
            if ($openslxConfig{'pxe-default-menu-entry'} eq
                $info->{'external-id'})
            {
                $pxeDefault = "\tMENU DEFAULT\n";
            }
        }
        $append .= " initrd=$pxePrefix$vendorOSName/$info->{'initramfs-name'}";
        $append .= " file=$bootURI"     if length($bootURI);
        $append .= " file=$tftpPrefix"  if length($tftpPrefix);
        $append .= " $clientAppend";
        $slxLabels .= "LABEL openslx-$pxeLabel\n";
        $slxLabels .= $pxeDefault;
        $slxLabels .= "\tMENU LABEL ^$info->{menuLabel}\n";
        $slxLabels .= "\tKERNEL $pxePrefix$vendorOSName/$kernelName\n";
        $slxLabels .= "\tAPPEND $append\n";
        $slxLabels .= "\tIPAPPEND 3\n";
#       if kernel=*xen* then run sub _xenBootEntry from xen.pm
#       if (!defined $xenKernel) {...}
        my $helpText = $info->{description} || '';
        if (length($helpText)) {
            # make sure that text matches the given margin
            my $menuMargin;
            while ($pxeConfig =~ m{^\s*MENU MARGIN (\S+?)\s*$}gims) {
                chomp($menuMargin = $1);
            }
            my $margin
                = defined $menuMargin
                    ? "$menuMargin"
                    : "0";
            my $marginAsText = ' ' x $margin;

            my $menuWidth;
            while ($pxeConfig =~ m{^\s*MENU WIDTH (\S+?)\s*$}gims) {
                chomp($menuWidth = $1);
            }
            my $width
                = defined $menuWidth
                    ? "$menuWidth"
                    : "80";
               $width = $width - 2* $margin + 2;

            my @atomicHelpText = split(/ /, $helpText);
            my $lineCounter = 0;

            $helpText = "";

            foreach my $word (@atomicHelpText){
                if ($lineCounter + length($word) + 1 < $width) {
                     $helpText .= "$word ";
                     $lineCounter += length($word) + 1;
                } else {
                     my $nobreak = 1;
                     while ($nobreak == 1) {
                        my $pos = index($word,"-");
                        $nobreak = 0;
                        if ($pos != -1) {
                            if ($lineCounter + $pos + 1 < $width) {
                                $helpText .= substr($word, 0, $pos+1);
                                $word = substr($word, $pos + 1, length($word));
                                $nobreak = 1;
                            }
                        }
                     }
                     $helpText .= "\n$word ";
                     $lineCounter = length($word);
                }
            } 

            $helpText =~ s{^}{$marginAsText}gms;
            $slxLabels .= "\tTEXT HELP\n";
            $slxLabels .= "$helpText\n";
            $slxLabels .= "\tENDTEXT\n";
        }
    }
    # now add the slx-labels (inline or appended) and write the config file
    if (!($pxeConfig =~ s{\@\@\@SLX_LABELS\@\@\@}{$slxLabels})) {
        $pxeConfig .= $slxLabels;
        # fetch PXE-bottom iclude, if exists (overwrite existing definitions)
        my $pxeBottomFile
            = "$openslxConfig{'config-path'}/boot-env/syslinux/pxemenu-bottom";
        if (-e $pxeBottomFile) {
            $pxeConfig .= "\n# configuration from include $pxeBottomFile\n";
            $pxeConfig .= slurpFile($pxeBottomFile);
        }
    }

    # PXE uses 'cp850' (codepage 850) but our string is in utf-8, we have
    # to convert in order to avoid showing gibberish on the client side...
    spitFile($pxeFile, $pxeConfig, { 'io-layer' => 'encoding(cp850)' } )
        unless $self->{'dry-run'};

    return 1;
}

sub _getTemplate
{
    my $self = shift;

    return $self->{'pxe-template'} if $self->{'pxe-template'};

    my $basePath   = $openslxConfig{'base-path'};
    my $configPath = $openslxConfig{'config-path'};
    my $pxeTheme   = $openslxConfig{'syslinux-theme'};

    my ($sec, $min, $hour, $day, $mon, $year) = (localtime);
    $mon++;
    $year += 1900;
    my $callDate = sprintf('%04d-%02d-%02d', $year, $mon, $day);
    my $callTime = sprintf('%02d:%02d:%02d', $hour, $min, $sec);
    
    # generate PXE-Menu
    my $pxeTemplate =
        "# generated by slxconfig-demuxer (on $callDate at $callTime)\n";
    if ($pxeTheme eq "simple") {
        $pxeTemplate .= "\nDEFAULT menu.c32\n";
    } else {
        $pxeTemplate .= "\nDEFAULT vesamenu.c32\n";
    }
    # include static defaults
    $pxeTemplate .= "\n# static configuration (override with include file)\n";
    $pxeTemplate .= "NOESCAPE 0\n";
    $pxeTemplate .= "PROMPT 0\n";

    # first check for theme
    # let user stuff in config path win over our stuff in base path
    my $pxeThemePath;
    my $pxeThemeInConfig
        = "$configPath/boot-env/syslinux/themes/${pxeTheme}";
    my $pxeThemeInBase
        = "$basePath/share/boot-env/syslinux/themes/${pxeTheme}";
    if (-e "$pxeThemeInConfig/theme.conf") {
        $pxeThemePath = $pxeThemeInConfig;
    }
    else {
        if (-e "$pxeThemeInBase/theme.conf") {
            $pxeThemePath = $pxeThemeInBase;
        }
    }
    # include theme specific stuff
    if (defined $pxeThemePath) {
        $pxeTemplate .= "\n# theme specific configuration from $pxeThemePath\n";
        $pxeTemplate .= slurpFile("$pxeThemePath/theme.conf");
    }

    # copy background picture if exists
    my $pic;
    if (defined $pxeTheme) {
        while ($pxeTemplate =~ m{^\s*MENU BACKGROUND (\S+?)\s*$}gims) {
            chomp($pic = $1);
        }
    }
    if (defined $pic) {
        my $pxeBackground = "$pxeThemePath/$pic";
        if (-e $pxeBackground && !$self->{'dry-run'}) {
            slxsystem(qq[cp "$pxeBackground" $self->{'target-path'}/]);
        }
    }

    # include slxsettings
    $pxeTemplate .= "\n# slxsettings configuration\n";
    $pxeTemplate .= "TIMEOUT $openslxConfig{'pxe-timeout'}\n" || "";
    $pxeTemplate .= "TOTALTIMEOUT $openslxConfig{'pxe-totaltimeout'}\n" || "";
    my $sha1pass  = $self->_sha1pass($openslxConfig{'pxe-passwd'});
    $pxeTemplate .= "MENU MASTER PASSWD $sha1pass\n" || "";
    $pxeTemplate .= "MENU TITLE $openslxConfig{'pxe-title'}\n" || "";

    # fetch PXE-include, if exists (overwrite existing definitions)
    my $pxeIncludeFile
        = "$openslxConfig{'config-path'}/boot-env/syslinux/pxemenu-include";
    if (-e $pxeIncludeFile) {
        $pxeTemplate .= "\n# configuration from include $pxeIncludeFile\n";
        $pxeTemplate .= slurpFile($pxeIncludeFile);
    }

    $pxeTemplate .= "\n# slxsystems:\n";
    $self->{'pxe-template'} = $pxeTemplate;
    
    return $pxeTemplate;
}
   
sub _prepareBootloaderConfigFolder
{
    my $self = shift;
    
    my $basePath      = $openslxConfig{'base-path'};
    my $pxePath       = $self->{'target-path'};
    my $pxeConfigPath = "$pxePath/pxelinux.cfg";
    my $configPath = $openslxConfig{'config-path'};

    if (!$self->{'dry-run'}) {
        rmtree($pxeConfigPath);
        mkpath($pxeConfigPath);

        for my $file ('pxelinux.0', 'pxechain.com', 'vesamenu.c32', 'menu.c32',
            'mboot.c32', 'kernel-shutdown', 'initramfs-shutdown') {
            if (!-e "$pxePath/$file") {
                slxsystem(
                    qq[cp -p "$basePath/share/boot-env/syslinux/$file" $pxePath/]
                );
            }
        }
        #copy iPXE to tftproot
        my @ipxeFiles = <$basePath/share/boot-env/iPXE/*>;
        foreach my $ipxeFile (@ipxeFiles) {
            slxsystem(
                qq[cp -p "$ipxeFile" $pxePath/]
            );
        }
        #copy static content to tftproot
        my @staticFiles = <$configPath/boot-env/syslinux/static/*>;
        foreach my $staticFile (@staticFiles) {
            slxsystem(
                qq[cp -pd "$staticFile" $pxePath/]
            );
        }

    }
    
    $self->{preparedBootloaderConfigFolder} = 1;

    return 1;
}

# from syslinux 3.73: http://syslinux.zytor.com
sub _random_bytes
{
    my $self = shift;
    my $n = shift;
    my($v, $i);

    # using perl rand because of problems with encoding(cp850) and 'bytes'
    srand($$ ^ time);
    $v = '';
    for ( $i = 0 ; $i < $n ; $i++ ) {
        $v .= ord(int(rand() * 256));
    }
    
    return $v;
}

sub _sha1pass
{
    my $self = shift;
    my $pass = shift;
    my $salt = shift || MIME::Base64::encode($self->_random_bytes(6), '');
    $pass = Digest::SHA::sha1_base64($salt, $pass);

    return sprintf('$4$%s$%s$', $salt, $pass);
}

1;