#!/usr/bin/perl -w

###########################################################################
#
# Baracus build and boot management framework
#
# Copyright (C) 2010 Novell, Inc, 404 Wyman Street, Waltham, MA 02451, USA.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the Artistic License 2.0, as published
# by the Perl Foundation, or the GNU General Public License 2.0
# as published by the Free Software Foundation; your choice.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  Both the Artistic
# Licesnse and the GPL License referenced have clauses with more details.
#
# You should have received a copy of the licenses mentioned
# along with this program; if not, write to:
#
# FSF, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110, USA.
# The Perl Foundation, 6832 Mulderstraat, Grand Ledge, MI 48837, USA.
#
###########################################################################

use strict;
use warnings;

use CGI;
use Apache::DBI ();
use DBI ();
use File::Path;

use lib "/usr/share/baracus/perl";

use BaracusSql qw( :vars );
use BaracusState qw( :vars :subs :admin :actions :events );
use BaracusConfig qw( :vars );
use BaracusCgi qw( :subs );
use BaracusCore qw( :subs );
use BaracusAux qw( :subs );
use BaracusHost qw( :subs );
use BaracusPower qw( :subs );
use BaracusStorage qw( :vars :subs );

use lib '/var/spool/baracus/www/modules';
use BATools qw( :standard );

BEGIN {
    use Time::localtime;

    open(LOG, ">>$baDir{logs}/baracusd") or
        die("Unable to open $baDir{logs}/baracusd: $!");
    my $oldfh = select(LOG); $| = 1; select($oldfh);

    sub printlog {
        my $msg = shift;
        print LOG "[" . ctime() . "] $ENV{ REMOTE_ADDR } $msg";
    }
}

my $oldfh = select(STDOUT); $| = 1; select($oldfh);

my $cgi = new CGI;

my $input = {};                 # for CGI args or PUT cookie

my $clonedone = "$baDir{hooks}/clone_fetch_complete";
my $clonefail = "$baDir{hooks}/clone_fetch_failed";
my $migratedone = "$baDir{hooks}/migrate_complete";
my $migratefail = "$baDir{hooks}/migrate_failed";
my $imagedone = "$baDir{hooks}/image_deploy_complete";
my $imagefail = "$baDir{hooks}/image_deploy_failed";
my $mcastdone = "$baDir{hooks}/mcast_deploy_complete";
my $mcastfail = "$baDir{hooks}/mcast_deploy_failed";
my $newinv    = "$baDir{hooks}/new_inventory_available";
my $incloner  = "$baDir{hooks}/node_in_clone_ready";
my $inmigrate = "$baDir{hooks}/node_in_migrate_ready";
my $inpxewait = "$baDir{hooks}/node_in_pxewait";
my $inmcast   = "$baDir{hooks}/node_in_mcastwait";
my $built     = "$baDir{hooks}/verify_client_build_passed";
my $spoofed   = "$baDir{hooks}/verify_client_build_failed";
my $wipedone  = "$baDir{hooks}/wipe_disk_complete";
my $wipefail  = "$baDir{hooks}/wipe_disk_failed";
my $ipmidone  = "$baDir{hooks}/ipmi_user_complete";
my $ipmifail  = "$baDir{hooks}/ipmi_user_failed";

my $linux_baracus = "linux.baracus";
my $linux_xen_baracus = "linux-xen.baracus";
my $initrd_baracus = "initrd.baracus";
my $initrd_xen_baracus = "initrd-xen.baracus";

my $dbh = DBI->connect
    ("DBI:Pg:dbname=baracus;port=5162",
     "wwwrun",
     "",
     {
      PrintError => 1,          # warn() on errors
      RaiseError => 0,          # don't die on error
      AutoCommit => 1,          # commit executes
      # immediately
      }
     );

my $tftph = DBI->connect
    ("DBI:Pg:dbname=sqltftp;port=5162",
     "wwwrun",
     "",
     {
      PrintError => 1,          # warn() on errors
      RaiseError => 0,          # don't die on error
      AutoCommit => 1,          # commit executes
      # immediately
      }
     );

my $state = 0;
my $pxenext = 0;
my $admin = 0;

my %values = ();
my @fields = ();
my $fields = "";
my $values = "";
my $sql = "";
my $sth = 0;

my $href    = {};
my $macref  = {};
my $actref  = {};
my $distref = {};
my $hardref = {};
my $imgref  = {};
my $dstref  = {};

my $filename = "";
my $output = "";
my $content_type = "text/plain";

if ( $ENV{REQUEST_METHOD} eq "HEAD" ) {
    my $opts =
    {
     LASTERROR => "",
     debug     => 0,
     };

    foreach my $key ( $cgi->param() ) {
        $input->{$key} = $cgi->param($key);
    }
    foreach my $key ( $cgi->cookie() ) {
        $input->{$key} = $cgi->cookie($key);
    }

    if ( $ENV{REQUEST_URI} =~ m|^\s*/ba/built\?.*\s*$| ) {
        print $cgi->header(-type=>"$content_type",
                           -Content_length=>1,
                           -accept_ranges=>'bytes');
    }
    elsif ( $ENV{REQUEST_URI} =~ m{^\s*/ba/(jumpstart.tar)\?.*\s*$} )
    {
        my $file = $1;

        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        if ( not defined $macref ) {
            $output = "$input->{mac} - user vs cgi... missing db mac entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        $actref = &get_db_data( $dbh, 'action', $input->{mac} );
        if (not defined $actref ) {
            $output = "$input->{mac} - user vs cgi... missing db action entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        $actref->{serverip} = $baVar{serverip};

        # the mac and action might have been produced with an inventory req

        my $actauto = &get_db_data( $dbh, 'actabld', $input->{mac} );
        unless ( defined $actauto->{autobuild} ) {
            # this entry has no related autobuild and shouldn't be asking
            $output = "$input->{mac} - user vs cgi... no 'autobuild' entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        $actref->{autobuild} = $actauto->{autobuild};
        $actref->{autobuild_ver} = $actauto->{autobuild_ver};

        my $actmod = &get_action_modules_hash( $dbh, $input->{mac} );
        if ( defined $actmod ) {
            $actref->{modules} = join ' ', keys %{$actmod};
        } else {
            $actref->{modules} = '';
        }

        &load_baracusconfig( $opts, $actref );
        &load_storage ( $opts, $dbh, $actref );
        &load_hardware( $opts, $dbh, $actref );
        &load_distro  ( $opts, $dbh, $actref );
        &load_addons  ( $opts, $dbh, $actref );
        &load_modules ( $opts, $dbh, $actref );
        &load_profile ( $opts, $dbh, $actref );

        &load_vars         ( $opts, $actref );

        # Check if broadcast address is set
        &check_broadcast( $opts, $actref );

        printlog "$input->{mac} - serving autobuilds $file\n";

        my $sysidcfg = &get_sysidcfg_expanded( $opts, $dbh, $actref );
        my $autobuild = &get_autobuild_expanded( $opts, $dbh, $actref );
        my $rules = "any              -               -     profile  finish\n";
        my $rulesok = "any - - profile finish\n# version=2 checksum=num";
        my $finish = "profetch \"http://$actref->{serverip}/ba/built?uuid=$actref->{uuid}&hostname=$actref->{hostname}\"";

        my $jmac = $actref->{mac};
        $jmac =~ s/://g;
        mkdir "$baDir{cache}/jumpstart.$jmac", 0755 or die ("Cannot create $baDir{cache}/jumpstart.$jmac directory\n");

        open( SYSIDCFG, ">$baDir{cache}/jumpstart.$jmac/sysidcfg");
        print SYSIDCFG $sysidcfg;
        close( SYSIDCFG );

        open( PROFILE, ">$baDir{cache}/jumpstart.$jmac/profile");
        print PROFILE $autobuild;
        close( PROFILE );

        open( RULES, ">$baDir{cache}/jumpstart.$jmac/rules");
        print RULES $rules;
        close( RULES );

        open( RULESOK, ">$baDir{cache}/jumpstart.$jmac/rules.ok");
        print RULESOK $rulesok;
        close( RULESOK );

        open( FINISH, ">$baDir{cache}/jumpstart.$jmac/finish");
        print FINISH $finish;
        close( FINISH );

        system("tar -cvpf $baDir{cache}/jumpstart.tar.$jmac -C $baDir{cache}/jumpstart.$jmac . &>/dev/null");

        rmtree("$baDir{cache}/jumpstart.$jmac");

        open( OUTPUT, "<$baDir{cache}/jumpstart.tar.$jmac");
        my $output = join '', <OUTPUT>;
        close( OUTPUT );

        ## Determine size of tarball
        my $size;
        {
            use bytes;
            $size = bytes::length($output);
        }

        print $cgi->header(-type=>'application/x-tar',
                           -Content_length=>$size,
                           -accept_ranges=>'bytes');

    }
    exit;

} elsif ( $ENV{REQUEST_METHOD} eq "PUT" ) {
    my $opts =
    {
     LASTERROR => "",
     debug     => 0,
     };

    ##
    ## Handle the uploading of files via "http PUT"
    ##
    ##   curl -b "mac=<mac>;status=<status>" -T <file> \
    ##       http://$baVar{serverip}/ba/inventory
    ##

    unless ( $CGI::VERSION > 3.43 ) {
        foreach my $key ( $cgi->param() ) {
            $input->{$key} = $cgi->param($key);
        }
    }
    foreach my $key ( $cgi->cookie() ) {
        $input->{$key} = $cgi->cookie($key);
    }

    if ( $ENV{REQUEST_URI} =~ m%^\s*/ba/(inventory)\s*$% ) {

        ##
        ## REGISTER - STORE FILE
        ##

        my $action = $1;

        die "Missing 'mac=' in HTTP_COOKIE\n" unless ( defined $input->{mac} );
        $input->{mac} = &check_mac($input->{mac});

        printlog "$input->{mac} - host inventory received\n";
        $filename = $input->{mac} . ".inventory";

        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        unless ( $macref ) {
            # user deleted entry while system was doing what baracus told it
            &add_db_mac( $dbh, $input->{mac}, BA_EVENT_FOUND );
            $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        }

        $href = &find_tftpfile( $opts, $tftph, $filename );

        # hum... always want the new inventory...
        # so we know we have to remove the old entry first
        if (defined $href and ($href->{name} eq $filename)) {
            &delete_tftpfile( $opts, $tftph, $filename );
        }

        $fields = "name,bin,description,size,enabled,insertion";
        $values = qq|'$filename',?,'inventory',$ENV{CONTENT_LENGTH},'1',CURRENT_TIMESTAMP(0)|;
        $sql = qq|INSERT INTO sqlfstable ( $fields ) VALUES ( $values )|;
        die "$!\n$tftph->errstr" unless ( $sth = $tftph->prepare( $sql ) );

        my $putdata;
        if ( $CGI::VERSION > 3.43 ) {
            # new CGI.pm way - already read from STDIN
            printlog "$input->{mac} - using CGI.pm new style\n";
            $putdata = $cgi->param('PUTDATA');
        } else {
            # old style read putdata from STDIN
            printlog "$input->{mac} - using CGI.pm _old_ style\n";
            read( STDIN, $putdata, $ENV{CONTENT_LENGTH} );
        }

        # now chop $putdata into BA_DBMAXLEN pieces to store
        my $len = $ENV{CONTENT_LENGTH};
        my $clen = BA_DBMAXLEN;
        my $bin;
        my @bytes = split(//, $putdata);
        my $bytes = 1;
        while ( $len > 0 ) {
            $clen = $len if ( $len < $clen );
            $bytes = join('', splice(@bytes, 0, $clen));
            $len -= $clen;
            $bin = pack( 'u', $bytes );
            unless ( $sth->execute( $bin ) ) {
                croak $sth->err;
                exit 0;
            }
        }

        $sth->finish();
        $state = BA_EVENT_REGISTER;
        my $ip = $ENV{ REMOTE_ADDR };

        if ( -f $newinv ) {
            printlog "$input->{mac} - hook $newinv\n";
            my $cmd = "$newinv $input->{mac} $ip" ;
            $cmd = "sudo $BATools::baPath/modules/pfork.bin ".$cmd ;
            $output .= `$cmd`;
        }

        my $chkref = &get_db_data( $dbh, 'action', $input->{mac} );
        $actref = $chkref if ( defined $chkref );
        $actref->{cmdline} = "# inventory uploaded";
        &event_state_change( $dbh, $state, $macref, $actref );
        &update_db_mac_state( $dbh, $input->{mac}, $state );
        if ( defined $chkref ) {
            &update_db_data( $dbh, 'action', $actref );
        } else {
            $actref->{mac} = $input->{mac};
            &add_db_data( $dbh, 'action', $actref );
        }

        $output = "upload success\n";

    } elsif ( $ENV{REQUEST_URI} =~ m%^\s*/ba/(wipe|image|mcast|clonelog|migratelog)\s*$% ) {

        ##
        ## WIPE|IMAGE|MCAST|CLONE - COMPLETE HOOK AND STORE LOG/TICKET
        ##

        my $action = $1; $action =~ s/log$//; $action =~ s/^wipe$/diskwipe/;

        die "Missing 'mac=' in COOKIE\n" unless ( defined $input->{mac} );
        die "Missing 'status=' in COOKIE\n" unless ( defined $input->{status} );

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        unless ( $macref ) {
            # user deleted entry while system was doing what baracus told it
            &add_db_mac( $dbh, $input->{mac}, BA_EVENT_FOUND );
            $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        }
        my $chkref = &get_db_data( $dbh, 'action', $input->{mac} );
        $actref = $chkref if ( defined $chkref );

        my $hook = "";

        if ( $baState{$action} eq BA_ACTION_DISKWIPE ) {
            if ($input->{status} eq "pass") {
                $state = BA_EVENT_WIPED;
                $hook = $wipedone;
            } else {
                $state = BA_EVENT_WIPEFAIL;
                $hook = $wipefail;
            }
        } elsif ( $baState{$action} eq BA_ACTION_IMAGE ) {
            if ($input->{status} eq "pass") {
                $state = BA_EVENT_IMAGED;
                $hook = $imagedone;
            } else {
                $state = BA_EVENT_IMAGEFAIL;
                $hook = $imagefail;
            }
        } elsif ( $baState{$action} eq BA_ACTION_MCAST ) {
            if ($input->{status} eq "pass") {
                $state = BA_EVENT_MCASTED;
                $hook = $mcastdone;
            } else {
                $state = BA_EVENT_MCASTFAIL;
                $hook = $mcastfail;
            }
        } elsif ( $baState{$action} eq BA_ACTION_CLONE ) {
            if ($input->{status} eq "pass") {
                $state = BA_EVENT_CLONED;
                $hook = $clonedone;
                my $sref = &get_db_data( $dbh, 'storage', $actref->{storageid});
                $sref->{type} = BA_STORAGE_IMAGE;
                &update_db_data( $dbh, 'storage', $sref);
            } else {
                $state = BA_EVENT_CLONEFAIL;
                $hook = $clonefail;
            }
        } elsif ( $baState{$action} eq BA_ACTION_MIGRATE ) {
            if ($input->{status} eq "pass") {
                $state = BA_EVENT_MIGRATED;
                $hook = $migratedone;
                my $sref = &get_db_data( $dbh, 'storage', $actref->{storageid});
# Is migration to other then NFS supported?
                $sref->{type} = BA_STORAGE_NFS;
                &update_db_data( $dbh, 'storage', $sref);
            } else {
                $state = BA_EVENT_MIGRATEFAIL;
                $hook = $migratefail;
            }
        } else {
            # really bad don't know this upload/put log type - or how we get here
            printlog "$input->{mac} - unknow action type $action log received\n";
            $output = "permission denied\n";
            print $cgi->header( -type => "text/plain",
                                -status => "401 unauthorized",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        printlog "$input->{mac} - $action completion log received ($input->{status})\n";

        $filename = "$input->{mac}.${action}";
        $href = &find_tftpfile( $opts, $tftph, $filename );

        # hum... always want the new log...
        # so we know we have to remove the old entry first
        if (defined $href and ($href->{name} eq $filename)) {
            &delete_tftpfile( $opts, $tftph, $filename );
        }

        $fields = "name,bin,description,size,enabled,insertion";
        $values = qq|'$filename',?,'$action complete log',$ENV{CONTENT_LENGTH},'1',CURRENT_TIMESTAMP(0)|;
        $sql = qq|INSERT INTO sqlfstable ( $fields ) VALUES ( $values )|;
        die "$!\n$tftph->errstr" unless ( $sth = $tftph->prepare( $sql ) );

        my $putdata;
        if ( $CGI::VERSION > 3.43 ) {
            # new CGI.pm way - already read from STDIN
            printlog "$input->{mac} - using CGI.pm new style\n";
            $putdata = $cgi->param('PUTDATA');
        } else {
            # old style read putdata from STDIN
            printlog "$input->{mac} - using CGI.pm _old_ style\n";
            read( STDIN, $putdata, $ENV{CONTENT_LENGTH} );
        }

        # now chop $putdata into BA_DBMAXLEN pieces to store
        my $len = $ENV{CONTENT_LENGTH};
        my $clen = BA_DBMAXLEN;
        my $bin;
        my @bytes = split(//, $putdata);
        my $bytes = 1;
        while ( $len > 0 ) {
            $clen = $len if ( $len < $clen );
            $bytes = join('', splice(@bytes, 0, $clen));
            $len -= $clen;
            $bin = pack( 'u', $bytes );
            unless ( $sth->execute( $bin ) ) {
                croak $sth->err;
                exit 0;
            }
        }
        $sth->finish();
        $output = "upload success\n";
        my $ip = $ENV{ REMOTE_ADDR };

        if ( -f $hook ) {
            printlog "$input->{mac} - hook $hook\n";
            my $cmd = "$hook $input->{mac} $ip";
            $cmd = "sudo $BATools::baPath/modules/pfork.bin ".$cmd ;
            $output .= `$cmd`;
        }

        $actref->{cmdline} = "# $action completion log received ($input->{status})";
        &event_state_change( $dbh, $state, $macref, $actref );
        &update_db_mac_state( $dbh, $input->{mac}, $state );
        if ( defined $chkref ) {
            &update_db_data( $dbh, 'action', $actref );
        } else {
            $actref->{mac} = $input->{mac};
            &add_db_data( $dbh, 'action', $actref );
        }

    } else {

        ##
        ## NO OTHER HTTP PUT ALLOWED
        ##

        $output = "permission denied\n";
        print $cgi->header( -type => "text/plain",
                            -status => "401 unauthorized",
                            -content_length => length
                            ($output)
                           ), $output;
        exit 0;
    }
} elsif ( $ENV{REQUEST_METHOD} eq "GET" ) {
    my $opts =
    {
     LASTERROR => "",
     debug     => 0,
     };

    ##
    ## Handle HTTP GET requests with or without CGI parameters
    ##
    ##   curl -s -o /tmp/pxemenu \
    ##       http://$baVar{serverip}/ba/boot?mac=<mac>&ip=<ip>
    ##

    foreach my $key ( $cgi->param() ) {
        $input->{$key} = $cgi->param($key);
    }
    foreach my $key ( $cgi->cookie() ) {
        $input->{$key} = $cgi->cookie($key);
    }

    if ( $ENV{REQUEST_URI} =~ m|^\s*/ba/env.*\s*$| ) {

        $output .= "CGI URL INPUT\n";
        foreach my $env ( keys %{$input} ) {

            $output .= "$env => $input->{ $env }\n";
        }

        $output .= "\n";
        $output .= "ENVIRONMENT\n";
        foreach my $env ( keys %ENV ) {
            $output .= "$env => $ENV{ $env }\n";
        }
    } elsif ( $ENV{REQUEST_URI} =~ m|^\s*/ba/ipmi\s*$| ) {

        ##
        ##  IPMI - COMPLETE HOOK AND REGISTER IPMI INTERFACE
        ##

        die "Missing 'mac' in COOKIE\n" unless ( defined $input->{mac});
        die "Missing 'nodeaddr' in COOKIE\n" unless ( defined $input->{nodeaddr} );
        die "Missing 'ipmimac' in COOKIE\n" unless ( defined $input->{ipmimac} );
        die "Missing 'ipmiaddr' in COOKIE\n" unless ( defined $input->{ipmiaddr});
        die "Missing 'status' in COOKIE\n" unless ( defined $input->{status} );
        die "Missing 'ipmimsg' in COOKIE\n" unless ( defined $input->{ipmimsg} );

        my $hook = "";

        ## Add ipmi interface to power table
        use Socket;
        my $hostname = "";
        my $ipaddr = inet_aton("$input->{nodeaddr}");
        $hostname = gethostbyaddr($ipaddr, AF_INET);
        $hostname =~ s/^([^\.]*).?.*/$1/ if ( $hostname ne "" );
        $hostname = "null" if ( $hostname eq "" );

        printlog "$input->{mac} - ipmi callback ($input->{status}: \'$input->{ipmimsg}\')\n";

        if ( $input->{status} eq "success") {
            # ok to call the hook on failure bug not the db add
            my %ipmi =
                (
                 'mac' => "$input->{mac}",
                 'hostname' => "$hostname",
                 'ctype' => 'ipmi',
                 'login'=> 'baracus',
                 'passwd'=> "$baVar{ipmipasswd}",
                 'bmcaddr'=> "$input->{ipmiaddr}",
                 'dbh'=> $dbh
                 );
            &add_powerdb_entry( \%ipmi );
            $hook = "$ipmidone";
        } else {
            $hook = "$ipmifail";
        }

        # if ipmi is fetched node has info we need, success or fail, so call hook

        if ( -f $hook ) {
            printlog "$input->{mac} - hook $hook\n";
            my $cmd = "$hook $input->{ipmimac} $input->{ipmiaddr} $input->{mac} $input->{nodeaddr} $hostname $input->{status} \'$input->{ipmimsg}\'";
            $cmd = "sudo $BATools::baPath/modules/pfork.bin ".$cmd ;
            $output .= `$cmd`;
        }
    } elsif ( $ENV{REQUEST_URI} =~ m|^\s*/ba/chain\?.*\s*$| ) {

        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        my $default = qq|#!gpxe
dhcp net0
set net0/210:string http://$baVar{serverip}/ba/
set net0/209:string boot?mac=\${mac}&ip=\${ip}
chain http://$baVar{serverip}/ba/pxelinux.0?mac=\${mac}
|;

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        $actref = &get_db_data( $dbh, 'action', $input->{mac} );
        if ( not defined $macref or
             not defined $actref
            ) {
            printlog "$input->{mac} - chain of unknown\n";
            $output = $default;
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        # found a mac and action entry - get the pxenext
        $pxenext = $actref->{pxenext};

        if ( &is_inventory_still_required( $opts, $tftph, $input, $macref ) ) {
            printlog "$input->{mac} - inventory still needed\n";
            $output = $default;
        }
        elsif ( $pxenext eq BA_ACTION_BUILD ) {
            printlog "$input->{mac} - chain of build\n";
            $distref = &get_distro( $opts, $dbh, $actref );

            if ( $distref->{os} =~ m/solaris/i ) {

                $output = qq|#!gpxe
chain tftp://$baVar{serverip}/pxegrub
#chain http://$baVar{serverip}/ba/boot?mac=$input->{mac}
|;
            } elsif ( $distref->{os} =~ m/win/i ) {

                $output = qq|#!gpxe
chain http://$baVar{serverip}/ba/boot?mac=$input->{mac}
|;

            } else {
                $output = $default;
            }
        } else {
            printlog "$input->{mac} - chain of non-build\n";
            $output = $default;
        }
    } elsif ( $ENV{REQUEST_URI} =~ m|^\s*/ba/boot\?.*\s*$| ) {

        ##
        ## PXE - DIRECT TO INVENTORY, LOCALBOOT, INSTALL, WIPE
        ##

        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        $actref = &get_db_data( $dbh, 'action', $input->{mac} );
        if ( not defined $macref or
             not defined $actref
            ) {
            ##
            ## INVENTORY - missing 'mac' or 'action' entry (or entries)
            ##

            printlog "$input->{mac} - found and need inventory\n";

            # add the mac entry as found... if it's the one missing
            unless ( defined $macref ) {
                &add_db_mac( $dbh, $input->{mac}, BA_EVENT_FOUND );
                $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
            }
            # set the check if we already have an action entry
            my $chkref = $actref if ( defined $actref );

            $actref->{cmdline} = "# pxeboot found";

            # this is where a state is first created by a trigger
            # and that trigger has to show movement to the next state
            # because we serve the inventory pxe now
            $state = BA_ACTION_INVENTORY;
            &action_state_change( $dbh, $state, $actref );

            &update_db_mac_state( $dbh, $input->{mac}, $state );
            if ( defined $chkref ) {
                # should never be the case that we have
                # action w/o mac first... but this don't hurt
                &update_db_data( $dbh, 'action', $actref );
            } else {
                $actref->{mac} = $input->{mac};
                &add_db_data( $dbh, 'action', $actref );
            }

            &get_inventory( $cgi, \%baVar, $input ); # routine exits
        }

        # found a mac and action entry - get the pxenext
        if ( defined $actref->{pxenext} and $actref->{pxenext} ne "" ) {
            $pxenext = $actref->{pxenext};
        } else {
            # if not set (odd) start with inventory
            $pxenext = BA_ACTION_INVENTORY;
        }

        # if admin is DISABLED or IGNORED then
        # do not care about serving anything
        $admin = $actref->{admin};

        # don't do list
        #
        # bahost remove - no mac will be found again
        # bado empty    - removes action entry, will be found again
        #
        # ignored, none ( also pxe auto disabled )
        # disabled, pxewait ( wiped, wipefail - push next action to pxewait )

        if ( $admin eq BA_ADMIN_IGNORED or
             $pxenext eq BA_ACTION_NONE
            ) {
            # we shouldn't be here - we've already fed host pxe info...
            printlog "$input->{mac} - bad tftp daemon, bad.\n";
            exit 1;
        }

        if ( $admin eq BA_ADMIN_DISABLED or
             $pxenext eq BA_ACTION_PXEWAIT
            ) {
            printlog "$input->{mac} - serving pxewait\n";
            &do_pxewait( $cgi, \%baVar, $input );
            # do_pxewait call exits
        }

        if ( $pxenext eq BA_ACTION_NETBOOT ) {

            printlog "$input->{mac} - netboot: $actref->{storageid}\n";
            &update_db_mac_state( $dbh, $macref->{mac}, $pxenext );
            if ( $actref->{oper} ne $pxenext or
                 $actref->{pxecurr} ne $pxenext
                ) {
                $actref->{oper} = $pxenext;
                $actref->{pxecurr} = $pxenext;
                $actref->{cmdline} = "# pxeboot network image";
                &update_db_data( $dbh, 'action', $actref );
            }
            &load_storage( $opts, $dbh, $actref );
            &do_netboot( $cgi, $actref, $baVar{serverip} );
            # do_netboot exits
        }

        if ( $pxenext eq BA_ACTION_RESCUE ) {

            printlog "$input->{mac} - rescue mode\n";

            $distref = &get_distro( $opts, $dbh, $actref );
            $hardref = &get_hardware( $opts, $dbh, $actref, $actref->{hardware_ver} );

            unless ( defined $distref and defined $hardref ) {
                printlog "$input->{mac} - missing distro or hardware in db\n";
                exit 1;
            }

            &update_db_mac_state( $dbh, $macref->{mac}, $pxenext );
            if ( $actref->{oper} ne $pxenext or
                 $actref->{pxecurr} ne $pxenext
                ) {
                $actref->{oper} = $pxenext;
                $actref->{pxecurr} = $pxenext;
                $actref->{cmdline} = "# pxeboot rescue mode";
                &update_db_data( $dbh, 'action', $actref );
            }

            my $args;
            my $rescue;

            if ( $distref->{os} eq "opensuse" or
                 $distref->{os} eq "sles"     or
                 $distref->{os} eq "sled"   ) {
                $rescue = qq|rescue=$distref->{sharetype}://$distref->{shareip}$distref->{basepath} rescue=1 textmode=1 |;

            } elsif ( $distref->{os} eq "fedora" or
                      $distref->{os} eq "centos" or
                      $distref->{os} eq "esx"    or
                      $distref->{os} eq "rhel" ) {
                $rescue = qq|text rescue ks=http://$baVar{serverip}/ba/auto?mac=$input->{mac}|;
            }

            $args = "$hardref->{bootargs} $actref->{raccess} $actref->{loghost}";

            &do_rescue( $cgi, $input->{mac}, $baVar{serverip}, "$args $rescue" );
            # do_rescue exits
        }

        # have we been directed to fetch inventory
        # or is it missing ? if so - get it now.

        if ( $pxenext eq BA_ACTION_INVENTORY or
             &is_inventory_still_required( $opts, $tftph, $input, $macref ) ) {
            ##
            ## INVENTORY - have 'mac' but still missing inventory
            ##
            my $args = "";

            $args .= "$actref->{raccess} " if (defined $actref->{raccess});

            $hardref = &get_hardware( $opts, $dbh, $actref, $actref->{hardware_ver} );
            if ( defined $hardref ) {
                $args .= "$hardref->{bootargs} " if (defined $hardref->{bootargs});
            }

            printlog "$input->{mac} - get inventory from host\n";

            &update_db_mac_state( $dbh, $macref->{mac}, $pxenext );
            if ( $actref->{oper} ne $pxenext or
                 $actref->{pxecurr} ne $pxenext
                ) {
                $actref->{oper} = $pxenext;
                $actref->{pxecurr} = $pxenext;
                $actref->{cmdline} = "# pxeboot inventory request";
                &update_db_data( $dbh, 'action', $actref );
            }

            &get_inventory( $cgi, \%baVar, $input, $args );
            # get_inventory call exits
        }

        if ( $pxenext eq BA_ACTION_LOCALBOOT ) {
            ##
            ## LOCALBOOT - host built so just have it boot from localdisk
            ##

            printlog "$input->{mac} - building, built, localboot\n";

            &update_db_mac_state( $dbh, $macref->{mac}, $pxenext );
            if ( $actref->{oper} ne $pxenext or
                 $actref->{pxecurr} ne $pxenext
                ) {
                $actref->{oper} = $pxenext;
                $actref->{pxecurr} = $pxenext;
                $actref->{cmdline} = "# pxeboot directing to localdisk";
                &update_db_data( $dbh, 'action', $actref );
            }

            &do_localboot( $cgi, $actref, $baVar{serverip} );
            # do_localboot exits
        }

        if ( $pxenext eq BA_ACTION_BUILD ) {
            ##
            ## BUILD
            ##

            printlog "$input->{mac} - building\n";

            $state = BA_EVENT_BUILDING;

            &event_state_change( $dbh, $state, $macref, $actref );
            &update_db_mac_state( $dbh, $macref->{mac}, $state );
            $actref->{cmdline} = "# pxeboot directing to network install";
            &update_db_data( $dbh, 'action', $actref );

            my $args;

            $distref = &get_distro( $opts, $dbh, $actref );
            $hardref = &get_hardware( $opts, $dbh, $actref, $actref->{hardware_ver} );
            &load_baracusconfig( $opts, $actref );
            &load_distro( $opts, $dbh, $actref );
            &load_storage( $opts, $dbh, $actref );
            &load_profile( $opts, $dbh, $actref );
            &load_vars( $opts, $actref );

            my $actauto = &get_db_data( $dbh, 'actabld', $input->{mac} );
            if ( defined $actauto->{autobuild} ) {
                $actref->{autobuild} = $actauto->{autobuild};
                $actref->{autobuild_ver} = $actauto->{autobuild_ver};
            } else {
                $actref->{autobuild} = "none";
            }

            if ( $distref->{os} eq "opensuse" or
                 $distref->{os} eq "sles"     or
                 $distref->{os} eq "sled"   ) {
                $args = qq|textmode=1 install=$distref->{sharetype}://$distref->{shareip}$distref->{basepath}|;
                if ( $actref->{autobuild} ne "none" ) {
                    $args .= qq| autoyast=http://$baVar{serverip}/ba/auto?mac=$input->{mac}|;
                }
                if ( defined $actref->{type} and $actref->{type} == BA_STORAGE_NFS ) {
                    $args .= qq| root=/dev/nfs nfsroot=$actref->{storageuri}|;
                }
                if ( defined $hardref->{driverupdate} ) {
                    my $dudref = get_db_data( $dbh, 'distro', $hardref->{driverupdate} );
                    if ( defined $dudref ) {
                        $args .= qq| dud=$dudref->{sharetype}://$dudref->{shareip}$dudref->{basepath}/|;
                    }
                }
                my $lcmac = lc $input->{mac};

                $output = qq|DEFAULT label_$actref->{hostname}
PROMPT 0
TIMEOUT 0

LABEL label_$actref->{hostname}
    kernel http://$baVar{serverip}/ba/linux?mac=$input->{mac}
    append initrd=http://$baVar{serverip}/ba/initrd?mac=$input->{mac} $hardref->{bootargs} $actref->{raccess} $args $actref->{loghost} netwait=60 netdevice=eth0 udev.rule="mac=$lcmac,name=eth0" dhcptimeout=60
|;

            } elsif ( $distref->{os} eq "fedora" or
                      $distref->{os} eq "centos" or
                      $distref->{os} eq "esx"    or
                      $distref->{os} eq "rhel" ) {
                $args = qq|text|;
                if ( $actref->{autobuild} ne "none" ) {
                    $args .= qq| ks=http://$baVar{serverip}/ba/auto?mac=$input->{mac}|;
                }

                $output = qq|DEFAULT label_$actref->{hostname}
PROMPT 0
TIMEOUT 0

LABEL label_$actref->{hostname}
    kernel http://$baVar{serverip}/ba/linux?mac=$input->{mac}
    append initrd=http://$baVar{serverip}/ba/initrd?mac=$input->{mac} $hardref->{bootargs} $actref->{raccess} $args $actref->{loghost}
|;

            } elsif ( $distref->{os} =~ m/ubuntu/ ) {
                $args = qq|hostname=unassigned-hostname locale=$actref->{lang} console-setup/ask_detect=$actref->{consoleaskdetect} console-setup/layoutcode=$actref->{consolelayoutcode}|;
                if ( $actref->{autobuild} ne "none" ) {
                    $args .= qq| url=http://$baVar{serverip}/ba/auto?mac=$input->{mac}|;
                } else {
                    $args .= qq| tasks=standard pkgsel/language-pack-patterns= pkgsel/install-language-support=false priority=low|;
                }


                $output = qq|DEFAULT label_$actref->{hostname}
PROMPT 0
TIMEOUT 0

LABEL label_$actref->{hostname}
    kernel http://$baVar{serverip}/ba/linux?mac=$input->{mac}
    append initrd=http://$baVar{serverip}/ba/initrd?mac=$input->{mac} $hardref->{bootargs} $actref->{raccess} $args $actref->{loghost}
|;

            } elsif ( $distref->{os} =~ m/debian/i) {
                $args = qq|hostname=unassigned-hostname domain=unassigned-domain locale=$actref->{lang} console-setup/ask_detect=$actref->{consoleaskdetect} console-setup/layoutcode=$actref->{consolelayoutcode} debian-installer/allow_unauthenticated=true |;
                if ( $actref->{autobuild} ne "none" ) {
                    $args .= qq|preseed/url=http://$baVar{serverip}/ba/auto?mac=$input->{mac} console-keymaps-at/keymap=$actref->{keymap}|;
                } else {
                    $args .= qq|tasks=standard pkgsel/language-pack-patterns= pkgsel/install-language-support=false priority=low debian-installer/allow_unauthenticated=true|;
                }


                $output = qq|DEFAULT label_$actref->{hostname}
PROMPT 0
TIMEOUT 0

LABEL label_$actref->{hostname}
    kernel http://$baVar{serverip}/ba/linux?mac=$input->{mac}
    append initrd=http://$baVar{serverip}/ba/initrd?mac=$input->{mac} $hardref->{bootargs} $actref->{raccess} $args $actref->{loghost}
|;

            } elsif ( $distref->{os} =~ m/xenserver/i ) {
                $args = qq|--- tftp://$baVar{serverip}/vmlinuz|;
                if ( $actref->{autobuild} ne "none" ) {
                    $args .= qq| $hardref->{bootargs} $actref->{raccess} answerfile=http://$baVar{serverip}/ba/auto?mac=$input->{mac} install --- tftp://$baVar{serverip}/install.img|;
                } else {
                    $args .= qq| $hardref->{bootargs} $actref->{raccess} --- tftp://$baVar{serverip}/install.img|;
                }

                $output = qq|DEFAULT label_$actref->{hostname}
PROMPT 0
TIMEOUT 0

LABEL label_$actref->{hostname}
    kernel tftp://$baVar{serverip}/mboot.c32
    append tftp://$baVar{serverip}/xen.gz $args
|;

            } elsif ( $distref->{os} =~ m/solaris/i ) {
                $output = qq|#!gpxe
chain tftp://$baVar{serverip}/pxegrub
|;

            } elsif ( $distref->{os} =~ m/win/i ) {

                # the mac isn't required here for operation but it identifies the requesting device 

                if ( $actref->{storageid} ) {
                    $output = qq|#!gpxe
set keep-san-nb 1
sanboot  $actref->{storageuri}
chain tftp://$baVar{serverip}/$distref->{arch}/startrom.0
|;
                } else {
                    $output = qq|#!gpxe
chain tftp://$baVar{serverip}/$distref->{arch}/startrom.0
|;

                }
            }
        }

        if ( $pxenext eq BA_ACTION_IMAGE or
             $pxenext eq BA_ACTION_MCAST or
             $pxenext eq BA_ACTION_CLONE or
             $pxenext eq BA_ACTION_MIGRATE
            ) {
            ##
            ##  IMAGE|MCAST|CLONE - SERVE IMAGE WORKLOAD
            ##
            my $args = "";

            $args .= "$actref->{raccess} " if (defined $actref->{raccess});

            $hardref = &get_hardware( $opts, $dbh, $actref, $actref->{hardware_ver} );
            if ( defined $hardref ) {
                $args .= "$hardref->{bootargs} " if (defined $hardref->{bootargs});
            }

            my $lcmac = lc $input->{mac};
	    
            my $linux_wrkld=get_arch_linux($input);
            my $initrd_wrkld=get_arch_initrd($input);

            if ($pxenext eq BA_ACTION_IMAGE) {
                printlog "$input->{mac} - serving imaging workload\n";
                $state = BA_EVENT_IMAGING;
                $actref->{cmdline} = "# pxeboot directing to image disk";

                $imgref = &get_db_data( $dbh, 'storage', $actref->{storageid} );

                $output =
                    qq|DEFAULT image
PROMPT 0
TIMEOUT 0
LABEL image
        kernel http://$baVar{serverip}/ba/$linux_wrkld
        append initrd=http://$baVar{serverip}/ba/$initrd_wrkld root=/dev/ram0 install=exec:/usr/bin/baracus.image baimagedst="$hardref->{rootdisk}" baimagesrc="$imgref->{storage}" baracus=$baVar{serverip} mac=$input->{mac} $args netwait=60 netdevice=eth0 udev.rule="mac=$lcmac,name=eth0" dhcptimeout=60 nosmp
|;
            } elsif ( $pxenext eq BA_ACTION_MCAST ) {
                printlog "$input->{mac} - serving mcasting workload\n";
                $state = BA_EVENT_MCASTING;
                $actref->{cmdline} = "# pxeboot directing to multicast image disk";
                my $mcastref = &get_db_data( $dbh, 'mcast', $actref->{mcastid} );

                # here iamgeid is in IP format (validated in bado image)
                # so we're handling a multicast rendez-vous address
                $output =
                    qq|DEFAULT mcast
PROMPT 0
TIMEOUT 0
LABEL mcast
        kernel http://$baVar{serverip}/ba/$linux_wrkld
        append initrd=http://$baVar{serverip}/ba/$initrd_wrkld root=/dev/ram0 install=exec:/usr/bin/baracus.mcast baimagedst="$hardref->{rootdisk}" bardvaddr="$mcastref->{rdvip}" baracus=$baVar{serverip} mac=$input->{mac} portbase=$mcastref->{port} $args netwait=60 netdevice=eth0 udev.rule="mac=$lcmac,name=eth0" dhcptimeout=60 nosmp
|;
            } elsif ( $pxenext eq BA_ACTION_CLONE ) {
                printlog "$input->{mac} - serving cloning workload\n";
                $state = BA_EVENT_CLONING;
                $actref->{cmdline} = "# pxeboot directing clone disk image";

                $imgref = &get_db_data( $dbh, 'storage', $actref->{storageid} );
                $imgref->{storage} =~ s|\.gz$||;

                $output =
                    qq|DEFAULT clone
PROMPT 0
TIMEOUT 0
LABEL clone
        kernel http://$baVar{serverip}/ba/$linux_wrkld
        append initrd=http://$baVar{serverip}/ba/$initrd_wrkld root=/dev/ram0 install=exec:/usr/bin/baracus.clone baimagedst="$imgref->{storage}" baimagesrc="$hardref->{rootdisk}" autoclone=$actref->{autoclone} baracus=$baVar{serverip} mac=$input->{mac} $args netwait=60 netdevice=eth0 udev.rule="mac=$lcmac,name=eth0" dhcptimeout=60 nosmp
|;
            } elsif ( $pxenext eq BA_ACTION_MIGRATE ) {
                printlog "$input->{mac} - serving migrate workload\n";
                $state = BA_EVENT_MIGRATING;
                $actref->{cmdline} = "# pxeboot directing migrate disk image";

                $dstref = &get_db_data( $dbh, 'storage', $actref->{storageid} );
                $output =
                    qq|DEFAULT migrate
PROMPT 0
TIMEOUT 0
LABEL migrate
        kernel http://$baVar{serverip}/ba/$linux_wrkld
        append initrd=http://$baVar{serverip}/ba/$initrd_wrkld root=/dev/ram0 install=exec:/usr/bin/baracus.migrate badstfs="$dstref->{storageip}:$dstref->{storage}" basrcfs="$hardref->{rootdisk}" automigrate=$actref->{automigrate} baracus=$baVar{serverip} mac=$input->{mac} $args netwait=60 netdevice=eth0 udev.rule="mac=$lcmac,name=eth0" dhcptimeout=60
|;
            }

            &event_state_change( $dbh, $state, $macref, $actref );
            &update_db_mac_state( $dbh, $macref->{mac}, $state );
            if ( $actref->{oper} ne $state ) {
                &update_db_data( $dbh, 'action', $actref );
            }

        }
        if ( $pxenext eq BA_ACTION_DISKWIPE ) {
            ##
            ## WIPE
            ##
            my $args = "";

            $args .= "$actref->{raccess} " if (defined $actref->{raccess});

            $hardref = &get_hardware( $opts, $dbh, $actref, $actref->{hardware_ver} );
            if ( defined $hardref ) {
                $args .= "$hardref->{bootargs} " if (defined $hardref->{bootargs});
            }

            printlog "$input->{mac} - wiping\n";

            $state = BA_EVENT_WIPING;

            &event_state_change( $dbh, $state, $macref, $actref );
            &update_db_mac_state( $dbh, $macref->{mac}, $pxenext );
            if ( $actref->{oper} ne $state ) {
                $actref->{cmdline} = "# pxeboot directing to wipe disk";
                &update_db_data( $dbh, 'action', $actref );
            }

            my $lcmac = lc $input->{mac};

            my $autonuke = "";
            $autonuke = " --autonuke" if ( $actref->{autonuke} );

            my $linux_nuk=get_arch_linux($input);
            my $initrd_nuk=get_arch_initrd($input);

            $output =
                qq|DEFAULT wipe
PROMPT 0
TIMEOUT 0
LABEL wipe
        kernel http://$baVar{serverip}/ba/$linux_nuk
        append initrd=http://$baVar{serverip}/ba/$initrd_nuk root=/dev/ram0 install=exec:/usr/bin/baracus.dban nuke="dwipe${autonuke}" baracus=$baVar{serverip} mac=$input->{mac} $args netwait=60 netdevice=eth0 udev.rule="mac=$lcmac,name=eth0" dhcptimeout=60 nosmp
|;

        }
    } elsif ( $ENV{REQUEST_URI} =~ m{^\s*/ba/(winst)\?.*\s*$} ) {

        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        $actref = &get_db_data( $dbh, 'action', $input->{mac} );
        $distref = &get_distro( $opts, $dbh, $actref );
        if ( not defined $macref or
             not defined $actref or
             not defined $distref
            ) {
            ##
            ## WINST - missing 'mac' or 'action' entry (or entries)
            ##
            printlog "$input->{mac} - missing mac, action, distro in db\n";
            exit 1;
        }

        my $actauto = &get_db_data( $dbh, 'actabld', $input->{mac} );
        if ( defined $actauto->{autobuild} ) {
            $output .= qq|\@echo off
\@echo curl.exe -G http://$baVar{serverip}/ba/auto?mac=$input->{mac} -o X:\\Autounattend.xml
curl.exe -G http://$baVar{serverip}/ba/auto?mac=$input->{mac} -o X:\\Autounattend.xml

net use n: \\\\$distref->{shareip}\\$actref->{distro}
n:\\setup.exe /unattend:X:\\Autounattend.xml
|;
        } else {
            $output .=qq|\@echo off
net use n: \\\\$distref->{shareip}\\$actref->{distro}
n:\\setup.exe
|;
        }

    } elsif ( $ENV{REQUEST_URI} =~ m{^\s*/ba/(parm)\?.*\s*$} ) {
        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        $actref = &get_db_data( $dbh, 'action', $input->{mac} );
        $distref = &get_distro( $opts, $dbh, $actref );
        if ( not defined $macref or
             not defined $actref or
             not defined $distref
            ) {
            ##
            ## WINST - missing 'mac' or 'action' entry (or entries)
            ##
            printlog "$input->{mac} - missing mac, action, distro in db\n";
            print $cgi->header( -status => "404 Not found");
            exit 0;
        }

        &load_baracusconfig( $opts, $actref );
        &load_storage( $opts, $dbh, $actref );
        &load_hardware( $opts, $dbh, $actref );
        &load_distro( $opts, $dbh, $actref );
        &load_profile( $opts, $dbh, $actref );

        if ( $actref->{arch} !~ m/s390/ ) {
            printlog "$input->{mac} - non-s390 arch request for parm file\n";
            exit 1;
        }

        &load_vars( $opts, $actref );

        # Check if broadcast address is set
        &check_broadcast( $opts, $actref );

        # parm file should only be served during installation
        $admin = $actref->{admin};
        if ( $admin eq BA_ADMIN_IGNORED or
             $admin eq BA_ADMIN_DISABLED ) {
            # Do not server anything in this case
            print $cgi->header( -status => "404 Not found");
            exit 0;
        } elsif (  $actref->{pxenext} eq BA_ACTION_BUILD ) {
            ##
            ## BUILD
            ##

            printlog "$input->{mac} - building\n";

            $state = BA_EVENT_BUILDING;

            &event_state_change( $dbh, $state, $macref, $actref );
            &update_db_mac_state( $dbh, $macref->{mac}, $state );
            $actref->{cmdline} = "# pxeboot directing to network install";
            &update_db_data( $dbh, 'action', $actref );

            ## example parm
            #
            # ramdisk_size=65536 root=/dev/ram1 ro init=/linuxrc TERM=dumb
            # ReadChannel=0.0.0700 WriteChannel=0.0.0701 DataChannel=0.0.0702
            # OsaInterface=qdio OsaMedium=eth
            # InstNetDev=osa layer2=0 PortNo=0
            # Portname=VSW1
            # Hostname=s390vm01.suse.de HostIP=10.10.220.97
            # Gateway=10.10.0.8 Nameserver=10.10.0.1
            # Netmask=255.255.0.0 Broadcast=10.10.255.255
            # Install=nfs://10.10.0.100/dist/install/SLP/SLES-11-GM/s390x/DVD1/
            # UseSSH=1 SSHPassword=testing linuxrcstderr=/dev/console

            $output = qq|ramdisk_size=65536 root=/dev/ram1 ro init=/linuxrc
$actref->{bootargs}
Hostname=$actref->{hostname}.$actref->{dnsdomain}
Install=$actref->{sharetype}://$actref->{shareip}$actref->{basepath}
linuxrcstderr=/dev/console
|;

            if ( $actref->{InstNetDev} eq "osa" ) {
                $output .= qq|InstNetDev=osa
OsaInterface=$actref->{OsaInterface}
OsaMedium=$actref->{OsaMedium}
layer2=$actref->{OsaLayer2}
PortNo=$actref->{OsaPortNo}
Portname=$actref->{OsaPortname}
ReadChannel=$actref->{ReadChannel}
WriteChannel=$actref->{WriteChannel}
DataChannel=$actref->{DataChannel}
|;
            } elsif ($actref->{InstNetDev} eq "hsi" ) {
                $output .= qq|InstNetDev=hsi
ReadChannel=$actref->{ReadChannel}
WriteChannel=$actref->{WriteChannel}
DataChannel=$actref->{DataChannel}
|;
            } else {
                printlog "Unsupported InstNetDev: $actref->{InstNetDev}\n";
                exit 1;
            }

            my $args = "";

            # Only OSA Layer2 can do dhcp
            if ( $actref->{ip} ne "dhcp" or $actref->{OsaLayer2} ne "1" ) {
                $args .= qq|HostIP=$actref->{ip}
Netmask=$actref->{netmask}
Broadcast=$actref->{broadcast}
Gateway=$actref->{gateway}
Nameserver=$actref->{dns1}
|;
            } else {
                printlog "No IP configuration specified\n";
                exit 1;
            }
            if ( $actref->{raccess} =~ m|(usessh=1)| ) {
                $args .= $1 . " " ;
                $args .= $1 . " " if ( $actref->{raccess} =~ m|(sshpassword=[^ \t\s]+)| );
            } elsif ( $actref->{raccess} =~ m|(vnc=1)| ) {
                $args .= "usevnc=1 " ;
                $args .= $1 . " " if ( $actref->{raccess} =~ m|(vncpassword=[^ \t\s]+)| );
            }

            my $actauto = &get_db_data( $dbh, 'actabld', $input->{mac} );
            if ( defined $actauto->{autobuild} ) {
                $actref->{autobuild} = $actauto->{autobuild};
                $actref->{autobuild_ver} = $actauto->{autobuild_ver};
                $args .= qq| autoyast=http://$baVar{serverip}/ba/auto?mac=$input->{mac}|;
            } else {
                $actref->{autobuild} = "none";
            }
            $output .= $args;

            my @tokens = split(' ', $output);
            my $line = "";
            my $outline;
            foreach my $token (@tokens) {
                if ( length($line) == 0 ) {
                    $line = $token;
                } elsif ( length($line) + length($token) < 78 ) {
                    $line = join(' ', $line, $token);
                } else {
                    $outline .= $line . "\n";
                    $line = " " . $token;
                }
            }

            $output = $outline . $line . "\n";

            # PARM file should not be longer than 10 lines
            my $count = $output =~ tr/\n//;
            unless ($count < 10) {
                printlog "PARM file too long: $count lines\n";
                exit 1;
            }
        } else {
            # Do not server anything in this case
            print $cgi->header( -status => "404 Not found");
            exit 0;
        }
    } elsif ( $ENV{REQUEST_URI} =~ m{^\s*/ba/(exec)\?.*\s*$} ) {
        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        $actref = &get_db_data( $dbh, 'action', $input->{mac} );
        $distref = &get_distro( $opts, $dbh, $actref );
        if ( not defined $macref or
             not defined $actref or
             not defined $distref
            ) {
            ##
            ## WINST - missing 'mac' or 'action' entry (or entries)
            ##
            printlog "$input->{mac} - missing mac, action, distro in db\n";
            print $cgi->header( -status => "404 Not found");
            exit 0;
        }

        &load_baracusconfig( $opts, $actref );
        &load_storage( $opts, $dbh, $actref );
        &load_hardware( $opts, $dbh, $actref );
        &load_distro( $opts, $dbh, $actref );
        &load_profile( $opts, $dbh, $actref );

        if ( $actref->{arch} !~ m/s390/ ) {
            printlog "$input->{mac} - non-s390 arch request for exec file\n";
            exit 1;
        }

        &load_vars( $opts, $actref );

        $admin = $actref->{admin};
        if ( $admin eq BA_ADMIN_IGNORED or
             $admin eq BA_ADMIN_DISABLED ) {
            # Do not server anything in this case
            print $cgi->header( -status => "404 Not found");
            exit 0;
        } elsif ( $actref->{pxenext} eq BA_ACTION_LOCALBOOT ) {
            ## example REXX script
            #
            # /* REXX */
            # Say 'Hello'
            # Address 'CMS' 'CP CLOSE RDR'
            # Address 'CMS' 'PURGE RDR ALL'
            # Address 'CMS' 'IPL 0150'

            $output = qq|/* REXX */
Address 'CMS' 'CP CLOSE RDR'
Address 'CMS' 'PURGE RDR ALL'
|;

            my $args = "";

            if ( $actref->{IplDevice} ) {
                $args .= qq|Address 'CMS' 'IPL $actref->{IplDevice}'
|;
            } else {
                $args .= qq|Address 'CMS' 'IPL 0150'
|;
            }
            $output .= $args;
        }
    } elsif ( $ENV{REQUEST_URI} =~ m{^\s*/ba/(startrom.0|pxelinux.0|sanboot.c32|chain.c32).*\s*$} ) {
        my $file = $1;

        printlog "# no mac required to serve $file\n";

        $sql = qq|SELECT bin FROM sqlfstable WHERE name = '$file'|;

        die "$!\n$tftph->errstr" unless ( $sth = $tftph->prepare( $sql ) );
        die "$!$sth->err\n" unless ( $sth->execute( ) );

        while ( $href = $sth->fetchrow_hashref( ) ) {
            $output .= unpack( 'u', $href->{'bin'} );
        }
        $sth->finish();
    } elsif ( $ENV{REQUEST_URI} =~ m{^\s*/ba/(auto)\?.*\s*$} ) {
        my $file = $1;

        #        my $date = &get_rundate();
        #        printlog "# start time for get $file : $date\n";

        ##
        ## AUTOBUILD FILE
        ##

        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        if ( not defined $macref ) {
            $output = "$input->{mac} - user vs cgi... missing db mac entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        $actref = &get_db_data( $dbh, 'action', $input->{mac} );
        if (not defined $actref ) {
            $output = "$input->{mac} - user vs cgi... missing db action entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        $actref->{serverip} = $baVar{serverip};

        # the mac and action might have been produced with an inventory req

        my $actauto = &get_db_data( $dbh, 'actabld', $input->{mac} );
        unless ( defined $actauto->{autobuild} ) {
            # this entry has no related autobuild and shouldn't be asking
            $output = "$input->{mac} - user vs cgi... no 'autobuild' entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        $actref->{autobuild} = $actauto->{autobuild};
        $actref->{autobuild_ver} = $actauto->{autobuild_ver};

        my $actmod = &get_action_modules_hash( $dbh, $input->{mac} );
        if ( defined $actmod ) {
            $actref->{modules} = join ' ', keys %{$actmod};
        } else {
            $actref->{modules} = '';
        }

        &load_baracusconfig( $opts, $actref );
        &load_storage ( $opts, $dbh, $actref );
        &load_hardware( $opts, $dbh, $actref );
        &load_distro  ( $opts, $dbh, $actref );
        &load_addons  ( $opts, $dbh, $actref );
        &load_modules ( $opts, $dbh, $actref );
        &load_profile ( $opts, $dbh, $actref );

        &load_vars         ( $opts, $actref );

        # Check if broadcast address is set
        &check_broadcast( $opts, $actref );

        printlog "$input->{mac} - serving autobuild $file\n";

        $output .= &get_autobuild_expanded( $opts, $dbh, $actref );

        #        $date = &get_rundate();
        #        printlog "# stop time for get $file : $date\n";
    } elsif ( $ENV{REQUEST_URI} =~ m{^\s*/ba/(menu.lst)\?.*\s*$} ) {
        my $file = $1;

        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        if ( not defined $macref ) {
            $output = "$input->{mac} - user vs cgi... missing db mac entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        $actref = &get_db_data( $dbh, 'action', $input->{mac} );
        if ( not defined $actref ) {
            $output = "$input->{mac} - user vs cgi... missing db action entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;

            exit 0;
        }

        my $isoref = &get_db_data( $dbh, 'iso', $actref->{distro}, 'distroid' );
        if ( not defined $isoref ) {
            $output = "$input->{mac} - user vs cgi... missing db iso entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;

            exit 0;
        }

        printlog "$input->{mac} - building\n";

        $state = BA_EVENT_BUILDING;

        &event_state_change( $dbh, $state, $macref, $actref );
        &update_db_mac_state( $dbh, $macref->{mac}, $state );
        $actref->{cmdline} = "# pxeboot directing to network install";
        &update_db_data( $dbh, 'action', $actref );

        &do_menu_lst( $cgi, $actref, $baVar{serverip}, $isoref->{mntpoint} );

    } elsif ( $ENV{REQUEST_URI} =~ m{^\s*/ba/(jumpstart.tar)\?.*\s*$} ) {
        my $file = $1;

        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        if ( not defined $macref ) {
            $output = "$input->{mac} - user vs cgi... missing db mac entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        $actref = &get_db_data( $dbh, 'action', $input->{mac} );
        if (not defined $actref ) {
            $output = "$input->{mac} - user vs cgi... missing db action entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        my $jsdata;
        my $jsfd;
        my $jmac = $actref->{mac};
        $jmac =~ s/://g;
        open ($jsfd, "<", "$baDir{cache}/jumpstart.tar.$jmac") || printlog "Error opening $jsfd";
        {
            binmode($jsfd);
            undef $/;
            $jsdata=<$jsfd>;
            $/ = "\n";
        }
        close $jsfd;

        unlink("$baDir{cache}/jumpstart.tar.$jmac");

        $output .= $jsdata;
        $content_type="application/x-tar";

    } elsif ( $ENV{REQUEST_URI} =~ m{^\s*/ba/(linux|initrd)\?.*\s*$} ) {
        my $file = $1;

        ##
        ## KERNEL OR INITRD OR AUTOBUILD FILE
        ##

        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        $input->{mac} = &check_mac($input->{mac});
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );
        if ( not defined $macref ) {
            $output = "$input->{mac} - user vs cgi... missing db mac entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        $actref = &get_db_data( $dbh, 'action', $input->{mac} );
        if (not defined $actref ) {
            $output = "$input->{mac} - user vs cgi... missing db action entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }

        if ($actref->{pxecurr} eq BA_ACTION_NETBOOT) {
	    if (not defined $actref->{storageid}) {
                $output = "$input->{mac} - user vs cgi... missing storageid entry";
                printlog "$output\n";
                print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
                exit 0;
            }

	    my $sref = &get_db_data( $dbh, 'storage', $actref->{storageid});

            if ((not defined $sref) || ($sref->{type} ne BA_STORAGE_NFS)) {
                $output = "$input->{mac} - user vs cgi... wrong storage type entry";
                printlog "$output\n";
                print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
                exit 0;
	    }

            ## NFS boot 

            my $nfs_script="$baDir{scripts}/mountnfs.sh";
            my $nfs_base="$baDir{nfsroot}";
	    my $nfsroot="$nfs_base/$sref->{storageip}/$sref->{storage}";
            my $fn;
            my $fd;
            my $fdata;

            printlog "netboot $actref->{storageid} $sref->{storageip} :: $1\n";
 
            my @args = ($nfs_script, "$nfs_base", "$sref->{storageip}", "$sref->{storage}");
	    my $sysrc = system(@args);
            unless ($sysrc == 0) {
                printlog "system @args returns: $sysrc \n";
            }

            if ($file eq "linux") {
		my @kernels= ("vmlinuz","vmlinuz-xen");
		foreach (@kernels)
		{
                    $fn = "$nfsroot/boot/$_";
            	    if ((-f $fn) && (-r $fn)) {
                        printlog "found: $fn \n";
			last;
		    }
		}
            } elsif ($file eq "initrd") {
		my @initrds= ("initrd","initrd-xen");
		foreach (@initrds)
                {
                    $fn = "$nfsroot/boot/$_";
                    if ((-f $fn) && (-r $fn)) {
                        printlog "found: $fn \n";
                        last;
                    }
                }
            }

            if ( -f $fn and -r $fn ) {
                printlog "$fn found\n";
            } else {
                $fn = &read_grubconf("$nfs_base/$sref->{storageip}/$sref->{storage}", "boot/grub/menu.lst", $file);
            }
            printlog "$input->{mac} - NFS boot $file @ $fn\n";

            open ($fd, "<", $fn) || printlog "Error opening $fn";
            {
            binmode($fd);
            undef $/;
            $fdata=<$fd>;
            $/ = "\n";
            }
            close $fd;

            my $flength = length( $fdata );
            printlog "$input->{mac} - read $fn length $flength bytes\n";
            $output .= $fdata;
	} else {

            my $filework = "$file.$actref->{distro}";

            ## wrinkle to support hardware dud initrd
            if ( $file =~ m/initrd/ ) {
                &load_hardware( $opts, $dbh, $actref );
                if ( $actref->{driverupdate} ne "" ) {
                    $filework = "$file.$actref->{driverupdate}";
                }
            }

            $file = $filework;

            printlog "$input->{mac} - serving $file\n";

            $sql = qq|SELECT bin FROM sqlfstable WHERE name = '$file'|;

            die "$!\n$tftph->errstr" unless ( $sth = $tftph->prepare( $sql ) );
            die "$!$sth->err\n" unless ( $sth->execute( ) );

            while ( $href = $sth->fetchrow_hashref( ) ) {
                $output .= unpack( 'u', $href->{'bin'} );
            }
            $sth->finish();
        } 

    } elsif ( $ENV{REQUEST_URI} =~ m%^\s*/ba/(pxewaitr|cloner|migrate)\?.*\s*$% ) {

        my $action = $1;
        $action =~ s/r$//;

        die "Missing 'mac=' in URL\n" unless ( defined $input->{mac} );

        my $mac = $input->{ mac };
        my $ip = $ENV{ REMOTE_ADDR };

        if ( $mac !~  m|^([0-9A-F]{1,2})(:([0-9A-F]){1,2}){5}$| ) {
            $output = "$input->{mac} - invalid or malformed mac provided in cgi call";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }
        $actref = &get_db_data( $dbh, 'action', $mac );
        if (not defined $actref ) {
            $output = "$input->{mac} - user vs cgi... missing db action entry";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }
        $actref->{cmdline} = "# $action ready notify event received";
        printlog "$input->{mac} - $action ready notify event received\n";
        $output = "$action hook called\n";

        my $hook = "";

        if ( $baState{$action} eq BA_ACTION_PXEWAIT ) {
            $hook = $inpxewait;
        } elsif ( $baState{$action} eq BA_ACTION_CLONE ) {
            $hook = $incloner;
        } elsif ( $baState{$action} eq BA_ACTION_MIGRATE ) {
            $hook = $inmigrate;
        }

        if ( -f $hook ) {
            printlog "$input->{mac} - hook $hook\n";
            my $cmd = "$hook $mac $ip";
            $cmd = "sudo $BATools::baPath/modules/pfork.bin ".$cmd ;
            $output .= `$cmd`;
        }
    } elsif ( $ENV{REQUEST_URI} =~ m|^\s*/ba/built\?.*\s*$| ) {

        my $hostname = $input->{ hostname };
        my $uuid = $input->{ uuid };
        my $ip = $ENV{ REMOTE_ADDR };

        # hash with a hostname
        $actref = &get_db_data_by( $dbh, 'action', $input->{hostname}, 'hostname' );
        unless ( defined $actref ) {
            $output = "$input->{hostname} - build callback unable to find $hostname";
            printlog "$output\n";
            print $cgi->header( -type => "text/plain",
                                -content_length => length
                                ($output)
                               ), $output;
            exit 0;
        }
        $input->{mac} = $actref->{ mac };
        $macref = &get_db_data( $dbh, 'mac', $input->{mac} );

        # check for spoofing of url access
        if (( "$uuid" ne "$actref->{'uuid'}" ) or
            (( "$ip"  ne "$actref->{'ip'}"  ) and
             ( "dhcp" ne "$actref->{'ip'}"  ))) {

            $state = BA_EVENT_SPOOFED;
            $actref->{cmdline} = "# build complete callback spoofed";
            printlog "$input->{mac} - $hostname $actref->{cmdline}\n";
            $output = "build spoofed - spoof hook called\n";

            if ( -f $spoofed ) {
                my $cmd = "$spoofed $hostname $ip $uuid $input->{mac}";
                $cmd = "sudo $BATools::baPath/modules/pfork.bin ".$cmd ;
                my $output = `$cmd`;
            }
        } else {
            $state = BA_EVENT_BUILT;
            $actref->{cmdline} = "# build complete callback verified";
            printlog "$input->{mac} - $hostname $actref->{cmdline}\n";
            $output = "build verified - verify hook called\n";

            if ( -f $built ) {
                my $cmd = "$built $hostname $ip $uuid $input->{mac}";
                $cmd = "sudo $BATools::baPath/modules/pfork.bin ".$cmd ;
                my $output = `$cmd`;
            }
        }

        &event_state_change( $dbh, $state, $macref, $actref );
        &update_db_mac_state( $dbh, $input->{mac}, $state );
        &update_db_data( $dbh, 'action', $actref );
    }

    ##
    ## kernel and initrd for inventory and dban
    ##

    elsif ( $ENV{REQUEST_URI} =~ m{^\s*/ba/($linux_baracus|$initrd_baracus|$linux_xen_baracus|$initrd_xen_baracus)\s*$} ) {

        printlog "# no mac required to serve $1\n";

        $sql = qq|SELECT bin FROM sqlfstable WHERE name = '$1'|;
        die "$!" unless ( $sth = $tftph->prepare( $sql ) );
        die "$!" unless ( $sth->execute( ) );

        while ( $href = $sth->fetchrow_hashref( ) ) {
            $output .= unpack( 'u', $href->{'bin'} );
        }
    } else {
        # should be some 401 or something
        $output = "invalid URL";
    }
} else {
    die "unsupported HTTP method:  $ENV{REQUEST_METHOD}\n";
}

print $cgi->header( -type => "$content_type", -content_length => length ($output)), $output;

exit 0;

1;
